ai-saas-guard 0.23.0 → 0.25.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/.bestpractices.json +138 -0
- package/CONTRIBUTING.md +74 -0
- package/README.md +23 -4
- package/README.zh-CN.md +30 -5
- package/docs/github-action.md +1 -1
- package/docs/github-app-deployment.md +10 -1
- package/docs/npm-publishing.md +3 -3
- package/docs/project-handoff.md +25 -2
- package/docs/release-quality-knowledge-base.md +1 -1
- package/docs/repository-trust-hardening.md +128 -0
- package/hosted/cloudflare-worker/README.md +51 -0
- package/hosted/cloudflare-worker/src/index.js +341 -0
- package/hosted/cloudflare-worker/wrangler.jsonc +21 -0
- package/package.json +7 -2
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-saas-guard",
|
|
3
|
+
"description": "Local-first launch preflight for AI-built SaaS repositories, focused on review-worthy auth, billing, data access, secrets, MCP, deploy, and pull request risks.",
|
|
4
|
+
"homepage_url": "https://github.com/zr9959/ai-saas-guard#readme",
|
|
5
|
+
"repo_url": "https://github.com/zr9959/ai-saas-guard",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"implementation_languages": "TypeScript, JavaScript",
|
|
8
|
+
"description_good_status": "Met",
|
|
9
|
+
"description_good_justification": "The README opens with the problem ai-saas-guard solves for AI-built SaaS launch review: https://github.com/zr9959/ai-saas-guard#the-problem-it-solves",
|
|
10
|
+
"interact_status": "Met",
|
|
11
|
+
"interact_justification": "The README explains how to obtain and use the CLI, and GitHub issue templates plus CONTRIBUTING.md explain feedback and contribution paths: https://github.com/zr9959/ai-saas-guard#quick-start and https://github.com/zr9959/ai-saas-guard/blob/main/CONTRIBUTING.md",
|
|
12
|
+
"contribution_status": "Met",
|
|
13
|
+
"contribution_justification": "CONTRIBUTING.md documents the pull request process, tests, docs, release gate, and safety requirements: https://github.com/zr9959/ai-saas-guard/blob/main/CONTRIBUTING.md",
|
|
14
|
+
"contribution_requirements_status": "Met",
|
|
15
|
+
"contribution_requirements_justification": "CONTRIBUTING.md documents acceptable contribution requirements, including focused PRs, tests, fixtures, docs, release gate evidence, and public-safety constraints: https://github.com/zr9959/ai-saas-guard/blob/main/CONTRIBUTING.md",
|
|
16
|
+
"floss_license_status": "Met",
|
|
17
|
+
"floss_license_justification": "The project is released under the MIT license: https://github.com/zr9959/ai-saas-guard/blob/main/LICENSE",
|
|
18
|
+
"floss_license_osi_status": "Met",
|
|
19
|
+
"floss_license_osi_justification": "MIT is an OSI-approved open source license, and the repository declares MIT in LICENSE and package.json: https://github.com/zr9959/ai-saas-guard/blob/main/LICENSE",
|
|
20
|
+
"license_location_status": "Met",
|
|
21
|
+
"license_location_justification": "The license is in the top-level LICENSE file: https://github.com/zr9959/ai-saas-guard/blob/main/LICENSE",
|
|
22
|
+
"documentation_basics_status": "Met",
|
|
23
|
+
"documentation_basics_justification": "README.md and docs/ explain installation, commands, release quality, rules, GitHub Action use, and hosted design boundaries: https://github.com/zr9959/ai-saas-guard#quick-start",
|
|
24
|
+
"documentation_interface_status": "Met",
|
|
25
|
+
"documentation_interface_justification": "README.md documents CLI commands and outputs, while docs/github-action.md documents the GitHub Action interface: https://github.com/zr9959/ai-saas-guard#commands and https://github.com/zr9959/ai-saas-guard/blob/main/docs/github-action.md",
|
|
26
|
+
"sites_https_status": "Met",
|
|
27
|
+
"sites_https_justification": "The public repository, release assets, issue tracker, and npm package are served over HTTPS: https://github.com/zr9959/ai-saas-guard and https://www.npmjs.com/package/ai-saas-guard",
|
|
28
|
+
"discussion_status": "Met",
|
|
29
|
+
"discussion_justification": "The project uses GitHub Issues and pull requests for public discussion: https://github.com/zr9959/ai-saas-guard/issues",
|
|
30
|
+
"english_status": "Met",
|
|
31
|
+
"english_justification": "The default README, docs, issue templates, pull request template, and contribution guide are in English, and the repository also provides a Chinese README for Chinese users: https://github.com/zr9959/ai-saas-guard",
|
|
32
|
+
"maintained_status": "Met",
|
|
33
|
+
"maintained_justification": "The project is actively maintained with recent commits, releases, issue closure, and CI on main: https://github.com/zr9959/ai-saas-guard/commits/main",
|
|
34
|
+
"repo_public_status": "Met",
|
|
35
|
+
"repo_public_justification": "The source repository is public on GitHub: https://github.com/zr9959/ai-saas-guard",
|
|
36
|
+
"repo_track_status": "Met",
|
|
37
|
+
"repo_track_justification": "The repository uses git on GitHub, preserving source history, authorship, and timestamps: https://github.com/zr9959/ai-saas-guard/commits/main",
|
|
38
|
+
"repo_interim_status": "Met",
|
|
39
|
+
"repo_interim_justification": "Interim source history is public through normal git commits on main, not only final release snapshots: https://github.com/zr9959/ai-saas-guard/commits/main",
|
|
40
|
+
"repo_distributed_status": "Met",
|
|
41
|
+
"repo_distributed_justification": "The project uses git, a distributed version control system: https://github.com/zr9959/ai-saas-guard",
|
|
42
|
+
"version_unique_status": "Met",
|
|
43
|
+
"version_unique_justification": "Releases use unique npm versions and GitHub tags such as v0.24.0: https://github.com/zr9959/ai-saas-guard/releases and https://www.npmjs.com/package/ai-saas-guard",
|
|
44
|
+
"version_semver_status": "Met",
|
|
45
|
+
"version_semver_justification": "The npm package and GitHub tags use semantic versioning with vMAJOR.MINOR.PATCH tags: https://github.com/zr9959/ai-saas-guard/tags",
|
|
46
|
+
"version_tags_status": "Met",
|
|
47
|
+
"version_tags_justification": "GitHub release tags are used for versioned releases and Action consumption: https://github.com/zr9959/ai-saas-guard/tags",
|
|
48
|
+
"release_notes_status": "Met",
|
|
49
|
+
"release_notes_justification": "GitHub Releases provide release notes for published versions: https://github.com/zr9959/ai-saas-guard/releases",
|
|
50
|
+
"release_notes_vulns_status": "Met",
|
|
51
|
+
"release_notes_vulns_justification": "No public vulnerability fix releases are currently known; the release gate requires release notes and security impact notes for releases: https://github.com/zr9959/ai-saas-guard/blob/main/docs/release-quality-knowledge-base.md",
|
|
52
|
+
"report_process_status": "Met",
|
|
53
|
+
"report_process_justification": "GitHub issue templates and SECURITY.md explain how to report bugs, false positives, false negatives, rule requests, and security-sensitive issues: https://github.com/zr9959/ai-saas-guard/issues/new/choose and https://github.com/zr9959/ai-saas-guard/blob/main/SECURITY.md",
|
|
54
|
+
"report_tracker_status": "Met",
|
|
55
|
+
"report_tracker_justification": "The project uses GitHub Issues as the public issue tracker: https://github.com/zr9959/ai-saas-guard/issues",
|
|
56
|
+
"report_responses_status": "Met",
|
|
57
|
+
"report_responses_justification": "The current public issue set is closed or handled, and issue templates define the response channels: https://github.com/zr9959/ai-saas-guard/issues",
|
|
58
|
+
"enhancement_responses_status": "Met",
|
|
59
|
+
"enhancement_responses_justification": "Enhancement and roadmap issues are tracked and closed through GitHub Issues: https://github.com/zr9959/ai-saas-guard/issues?q=is%3Aissue+label%3Aenhancement",
|
|
60
|
+
"report_archive_status": "Met",
|
|
61
|
+
"report_archive_justification": "GitHub Issues preserves the public issue archive: https://github.com/zr9959/ai-saas-guard/issues?q=is%3Aissue",
|
|
62
|
+
"vulnerability_report_process_status": "Met",
|
|
63
|
+
"vulnerability_report_process_justification": "SECURITY.md describes when to use private vulnerability reporting and what public issues must not contain: https://github.com/zr9959/ai-saas-guard/blob/main/SECURITY.md",
|
|
64
|
+
"vulnerability_report_private_status": "Met",
|
|
65
|
+
"vulnerability_report_private_justification": "SECURITY.md points security-sensitive reports to GitHub private vulnerability reporting: https://github.com/zr9959/ai-saas-guard/blob/main/SECURITY.md",
|
|
66
|
+
"vulnerability_report_response_status": "N/A",
|
|
67
|
+
"vulnerability_report_response_justification": "No public vulnerability reports are currently known for this project; future security-sensitive reports are routed through GitHub private vulnerability reporting: https://github.com/zr9959/ai-saas-guard/blob/main/SECURITY.md",
|
|
68
|
+
"build_status": "Met",
|
|
69
|
+
"build_justification": "The project has a one-step build through npm scripts: npm run build: https://github.com/zr9959/ai-saas-guard/blob/main/package.json",
|
|
70
|
+
"build_common_tools_status": "Met",
|
|
71
|
+
"build_common_tools_justification": "The project uses common Node.js, npm, and TypeScript tooling: https://github.com/zr9959/ai-saas-guard/blob/main/package.json",
|
|
72
|
+
"build_floss_tools_status": "Met",
|
|
73
|
+
"build_floss_tools_justification": "The build uses FLOSS Node.js, npm, and TypeScript tooling declared in package.json: https://github.com/zr9959/ai-saas-guard/blob/main/package.json",
|
|
74
|
+
"test_status": "Met",
|
|
75
|
+
"test_justification": "The automated test suite is run by npm test: https://github.com/zr9959/ai-saas-guard/blob/main/package.json",
|
|
76
|
+
"test_invocation_status": "Met",
|
|
77
|
+
"test_invocation_justification": "npm test builds the project and runs Node tests, and npm run test:fuzz runs the fast-check fuzz/property suite: https://github.com/zr9959/ai-saas-guard/blob/main/package.json",
|
|
78
|
+
"test_continuous_integration_status": "Met",
|
|
79
|
+
"test_continuous_integration_justification": "GitHub Actions runs CI on pull requests and pushes to main: https://github.com/zr9959/ai-saas-guard/blob/main/.github/workflows/ci.yml",
|
|
80
|
+
"test_policy_status": "Met",
|
|
81
|
+
"test_policy_justification": "CONTRIBUTING.md requires tests for behavior changes and vulnerable plus safe fixtures for scanner rules: https://github.com/zr9959/ai-saas-guard/blob/main/CONTRIBUTING.md",
|
|
82
|
+
"tests_are_added_status": "Met",
|
|
83
|
+
"tests_are_added_justification": "The repository contains tests for scanner behavior, hosted contracts, GitHub Action behavior, docs guards, and fuzz/property checks: https://github.com/zr9959/ai-saas-guard/tree/main/tests",
|
|
84
|
+
"tests_documented_added_status": "Met",
|
|
85
|
+
"tests_documented_added_justification": "CONTRIBUTING.md documents when tests and fixtures must be added: https://github.com/zr9959/ai-saas-guard/blob/main/CONTRIBUTING.md",
|
|
86
|
+
"warnings_status": "Met",
|
|
87
|
+
"warnings_justification": "TypeScript strict mode is enabled and CI runs workflow static checks through actionlint and zizmor: https://github.com/zr9959/ai-saas-guard/blob/main/tsconfig.json and https://github.com/zr9959/ai-saas-guard/blob/main/.github/workflows/ci.yml",
|
|
88
|
+
"warnings_fixed_status": "Met",
|
|
89
|
+
"warnings_fixed_justification": "The release gate requires a clean build, test suite, workflow checks, self-scan, dependency audit, and package inspection before public release: https://github.com/zr9959/ai-saas-guard/blob/main/docs/release-quality-knowledge-base.md",
|
|
90
|
+
"warnings_strict_status": "Met",
|
|
91
|
+
"warnings_strict_justification": "TypeScript strict mode is enabled in tsconfig.json, and CI includes actionlint plus zizmor for workflow quality and security checks: https://github.com/zr9959/ai-saas-guard/blob/main/tsconfig.json",
|
|
92
|
+
"know_secure_design_status": "Met",
|
|
93
|
+
"know_secure_design_justification": "The docs and rule catalog document secure design concerns for auth, billing, data access, secrets, MCP, hosted privacy, webhook verification, and release gating: https://github.com/zr9959/ai-saas-guard/blob/main/docs/rules.md and https://github.com/zr9959/ai-saas-guard/blob/main/docs/github-app-design.md",
|
|
94
|
+
"know_common_errors_status": "Met",
|
|
95
|
+
"know_common_errors_justification": "The project focuses on common SaaS launch errors such as missing ownership checks, unsafe Stripe webhooks, broad Supabase policies, secret exposure, unsafe MCP tools, and risky deploy settings: https://github.com/zr9959/ai-saas-guard/blob/main/docs/rules.md",
|
|
96
|
+
"crypto_published_status": "Met",
|
|
97
|
+
"crypto_published_justification": "Hosted helper code uses documented GitHub App RS256 JWT signing and HMAC SHA-256 webhook verification patterns rather than custom cryptography: https://github.com/zr9959/ai-saas-guard/blob/main/src/hosted/production-adapters.ts and https://github.com/zr9959/ai-saas-guard/blob/main/src/hosted/contracts.ts",
|
|
98
|
+
"crypto_call_status": "Met",
|
|
99
|
+
"crypto_call_justification": "Cryptographic operations use Node.js standard crypto APIs and GitHub-documented algorithms for GitHub App JWTs and webhook verification: https://github.com/zr9959/ai-saas-guard/blob/main/src/hosted/production-adapters.ts and https://github.com/zr9959/ai-saas-guard/blob/main/src/hosted/contracts.ts",
|
|
100
|
+
"crypto_floss_status": "Met",
|
|
101
|
+
"crypto_floss_justification": "The project uses FLOSS Node.js runtime cryptography through standard APIs: https://github.com/zr9959/ai-saas-guard/blob/main/package.json",
|
|
102
|
+
"crypto_keylength_status": "Met",
|
|
103
|
+
"crypto_keylength_justification": "The hosted design relies on GitHub App private-key material generated and managed for GitHub App authentication and does not define custom key sizes or algorithms: https://github.com/zr9959/ai-saas-guard/blob/main/docs/github-app-design.md",
|
|
104
|
+
"crypto_working_status": "Met",
|
|
105
|
+
"crypto_working_justification": "The project uses current GitHub App RS256 JWT signing and HMAC SHA-256 webhook verification mechanisms, not obsolete custom algorithms: https://github.com/zr9959/ai-saas-guard/blob/main/docs/github-app-design.md",
|
|
106
|
+
"crypto_weaknesses_status": "Met",
|
|
107
|
+
"crypto_weaknesses_justification": "The project does not use intentionally weak algorithms such as MD5 or SHA-1 for security decisions; hosted verification uses HMAC SHA-256 and JWT signing uses RS256: https://github.com/zr9959/ai-saas-guard/blob/main/src/hosted/contracts.ts",
|
|
108
|
+
"crypto_pfs_status": "N/A",
|
|
109
|
+
"crypto_pfs_justification": "The local CLI does not implement TLS transport; public distribution is through HTTPS GitHub and npm endpoints: https://github.com/zr9959/ai-saas-guard",
|
|
110
|
+
"crypto_password_storage_status": "N/A",
|
|
111
|
+
"crypto_password_storage_justification": "The local CLI and hosted helper contracts do not store user passwords: https://github.com/zr9959/ai-saas-guard#privacy-model",
|
|
112
|
+
"crypto_random_status": "N/A",
|
|
113
|
+
"crypto_random_justification": "The project does not generate cryptographic random values for security decisions in the local CLI: https://github.com/zr9959/ai-saas-guard",
|
|
114
|
+
"delivery_mitm_status": "Met",
|
|
115
|
+
"delivery_mitm_justification": "Source, releases, and npm package distribution use HTTPS endpoints: https://github.com/zr9959/ai-saas-guard/releases and https://www.npmjs.com/package/ai-saas-guard",
|
|
116
|
+
"delivery_unsigned_status": "Met",
|
|
117
|
+
"delivery_unsigned_justification": "GitHub releases attach npm provenance-backed tarballs plus sigstore and in-toto provenance assets, and npm trusted publishing provides provenance: https://github.com/zr9959/ai-saas-guard/releases and https://github.com/zr9959/ai-saas-guard/blob/main/docs/repository-trust-hardening.md",
|
|
118
|
+
"vulnerabilities_fixed_60_days_status": "Met",
|
|
119
|
+
"vulnerabilities_fixed_60_days_justification": "No unresolved high or critical production dependency vulnerabilities are currently known; the release gate requires npm audit at high severity or above: https://github.com/zr9959/ai-saas-guard/blob/main/docs/release-quality-knowledge-base.md",
|
|
120
|
+
"vulnerabilities_critical_fixed_status": "Met",
|
|
121
|
+
"vulnerabilities_critical_fixed_justification": "No unresolved critical vulnerabilities are currently known; SECURITY.md and the release gate define the response and release evidence path: https://github.com/zr9959/ai-saas-guard/blob/main/SECURITY.md",
|
|
122
|
+
"no_leaked_credentials_status": "Met",
|
|
123
|
+
"no_leaked_credentials_justification": "The repository has secret scanning and push protection enabled, uses inert fixtures, and the release gate forbids real credentials: https://github.com/zr9959/ai-saas-guard/blob/main/docs/repository-trust-hardening.md",
|
|
124
|
+
"static_analysis_status": "Met",
|
|
125
|
+
"static_analysis_justification": "The repository runs CodeQL SAST and workflow static checks with actionlint and zizmor: https://github.com/zr9959/ai-saas-guard/blob/main/.github/workflows/codeql.yml and https://github.com/zr9959/ai-saas-guard/blob/main/.github/workflows/ci.yml",
|
|
126
|
+
"static_analysis_common_vulnerabilities_status": "Met",
|
|
127
|
+
"static_analysis_common_vulnerabilities_justification": "CodeQL analyzes JavaScript and TypeScript for common vulnerability patterns, and ai-saas-guard self-scan checks SaaS launch-specific risks: https://github.com/zr9959/ai-saas-guard/blob/main/.github/workflows/codeql.yml",
|
|
128
|
+
"static_analysis_fixed_status": "Met",
|
|
129
|
+
"static_analysis_fixed_justification": "The release gate requires CI, CodeQL, actionlint, zizmor, self-scan, dependency audit, and issue tracking for findings before release: https://github.com/zr9959/ai-saas-guard/blob/main/docs/release-quality-knowledge-base.md",
|
|
130
|
+
"static_analysis_often_status": "Met",
|
|
131
|
+
"static_analysis_often_justification": "CodeQL runs on pull requests, pushes to main, and a weekly schedule; CI workflow checks run on pull requests and pushes to main: https://github.com/zr9959/ai-saas-guard/blob/main/.github/workflows/codeql.yml",
|
|
132
|
+
"dynamic_analysis_status": "Met",
|
|
133
|
+
"dynamic_analysis_justification": "The project uses fast-check fuzz/property tests for attacker-controlled markdown, SARIF, and redaction paths: https://github.com/zr9959/ai-saas-guard/blob/main/tests/fuzz.test.js",
|
|
134
|
+
"dynamic_analysis_unsafe_status": "N/A",
|
|
135
|
+
"dynamic_analysis_unsafe_justification": "The project is implemented in TypeScript and JavaScript rather than memory-unsafe C or C++: https://github.com/zr9959/ai-saas-guard/blob/main/package.json",
|
|
136
|
+
"dynamic_analysis_fixed_status": "Met",
|
|
137
|
+
"dynamic_analysis_fixed_justification": "The fuzz/property tests are part of CI and the release gate requires tests to pass before release: https://github.com/zr9959/ai-saas-guard/blob/main/.github/workflows/ci.yml"
|
|
138
|
+
}
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
`ai-saas-guard` is a local-first launch preflight CLI for AI-built SaaS repositories. Contributions should keep that promise narrow: find review-worthy launch risks, show evidence, and avoid broad security claims.
|
|
4
|
+
|
|
5
|
+
## Pull Request Process
|
|
6
|
+
|
|
7
|
+
1. Open an issue or comment on an existing issue before large feature work.
|
|
8
|
+
2. Keep pull requests focused. Separate scanner behavior, docs, workflow changes, and release work when practical.
|
|
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.
|
|
11
|
+
5. Fill out the pull request template with release gate evidence and known limitations.
|
|
12
|
+
|
|
13
|
+
## Local Development
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm ci
|
|
17
|
+
npm test
|
|
18
|
+
npm run build
|
|
19
|
+
node dist/cli.js --help
|
|
20
|
+
node dist/cli.js scan --root . --json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For release candidates or public repository changes, follow [docs/release-quality-knowledge-base.md](docs/release-quality-knowledge-base.md). The release gate is the source of truth for required checks, packaging inspection, dependency audit, self-scan evidence, and rollback notes.
|
|
24
|
+
|
|
25
|
+
## Testing Expectations
|
|
26
|
+
|
|
27
|
+
- Run `npm test` before sending a pull request.
|
|
28
|
+
- Run `npm run test:fuzz` when changing markdown rendering, SARIF rendering, secret redaction, parser behavior, or other attacker-controlled text handling.
|
|
29
|
+
- Keep fixtures public-safe and minimal.
|
|
30
|
+
- Prefer deterministic tests over live external services.
|
|
31
|
+
- Do not remove or weaken tests without explaining the review and replacement coverage.
|
|
32
|
+
|
|
33
|
+
## Rule Design
|
|
34
|
+
|
|
35
|
+
Scanner rules should be evidence-first:
|
|
36
|
+
|
|
37
|
+
- stable rule ID
|
|
38
|
+
- severity
|
|
39
|
+
- file/path evidence
|
|
40
|
+
- why the issue matters for a SaaS launch
|
|
41
|
+
- suggested manual verification
|
|
42
|
+
- practical fix direction
|
|
43
|
+
- vulnerable fixture
|
|
44
|
+
- safe fixture
|
|
45
|
+
- public rule documentation
|
|
46
|
+
|
|
47
|
+
Avoid turning the project into a generic SAST platform. The useful surface is AI-SaaS launch readiness: auth, billing, data access, secrets, MCP tools, deploy configuration, and risky pull request diffs.
|
|
48
|
+
|
|
49
|
+
## Security And Public Safety
|
|
50
|
+
|
|
51
|
+
- No real API keys, tokens, cookies, webhook signing secrets, database URLs, customer data, private source code, or private URLs.
|
|
52
|
+
- Use inert fake values in fixtures and examples.
|
|
53
|
+
- Do not add network calls to local scan commands unless a future feature is explicitly designed, documented, and tested as opt-in.
|
|
54
|
+
- Do not add shell execution to scan commands unless it is explicit, narrow, and separately reviewed.
|
|
55
|
+
- Public issues must stay safe to read. Use GitHub private vulnerability reporting for sensitive vulnerability details.
|
|
56
|
+
|
|
57
|
+
## Coding Standards
|
|
58
|
+
|
|
59
|
+
- Keep TypeScript strict and readable.
|
|
60
|
+
- Prefer small, focused helpers over broad abstractions.
|
|
61
|
+
- Keep CLI output useful for human reviewers and machine output parseable.
|
|
62
|
+
- Redact secret-like evidence.
|
|
63
|
+
- Bound resource use for repository scanning.
|
|
64
|
+
- Keep GitHub Actions permissions minimal and avoid untrusted input interpolation in shell scripts.
|
|
65
|
+
|
|
66
|
+
## Feedback Channels
|
|
67
|
+
|
|
68
|
+
Use GitHub issues for bugs, false positives, false negatives, and rule requests:
|
|
69
|
+
|
|
70
|
+
https://github.com/zr9959/ai-saas-guard/issues
|
|
71
|
+
|
|
72
|
+
Use private vulnerability reporting for security-sensitive reports:
|
|
73
|
+
|
|
74
|
+
https://github.com/zr9959/ai-saas-guard/security/advisories/new
|
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
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
|
+
<a href="https://www.bestpractices.dev/projects/12955"><img alt="OpenSSF Best Practices" src="https://www.bestpractices.dev/projects/12955/badge"></a>
|
|
21
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>
|
|
22
23
|
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
|
|
23
24
|
<a href="package.json"><img alt="Node.js >=20" src="https://img.shields.io/badge/node-%3E%3D20-339933.svg"></a>
|
|
@@ -73,14 +74,18 @@ The CLI is published on npm as `ai-saas-guard`, and the GitHub Action is availab
|
|
|
73
74
|
| JSON and SARIF output | Available |
|
|
74
75
|
| Composite GitHub Action | Available |
|
|
75
76
|
| 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.
|
|
77
|
+
| Versioned Action tags | `v0.25.0`, `v0` |
|
|
78
|
+
| npm package | `ai-saas-guard@0.25.0` |
|
|
78
79
|
| npm publishing | Trusted Publisher/OIDC, no long-lived publish token |
|
|
79
|
-
|
|
|
80
|
+
| Repository trust hardening | Strict branch protection, Dependabot, CodeQL, fast-check fuzzing, signed release provenance assets, private vulnerability reporting, secret scanning, and push protection |
|
|
81
|
+
| Runtime hardening | Per-file and total text scan caps, escaped markdown evidence, 1 MiB hosted webhook payload cap, stricter hosted deployment blockers |
|
|
80
82
|
| Hosted production adapters | GitHub App JWT signing, installation-token request planning, bounded worker execution, and terminal-state cleanup planning |
|
|
81
83
|
| Hosted app skeleton | Node/container HTTP ingress, health route, worker tick, in-memory provider adapters, and deployment plan validation |
|
|
82
84
|
| Hosted staging deployment planner | Provider binding, staging release-gate evidence, Node/container deployment composition, and GitHub App promotion gating |
|
|
83
85
|
| Hosted staging harness | File-backed webhook replay, queue/report/Check Run artifacts, worker cleanup verification, and local release-gate evidence fixtures |
|
|
86
|
+
| Cloudflare hosted ingress | Deployed at `https://ai-saas-guard-hosted.zr9959.workers.dev` for signed GitHub webhook intake and KV queueing; scan workers and Check Runs are still gated |
|
|
87
|
+
| Hosted GitHub App staging | Private App `ai-saas-guard-hosted` (`3834787`) installed on `zr9959/ai-saas-guard` with contents read, pull requests read, metadata read, and checks write |
|
|
88
|
+
| OpenSSF Best Practices | Passing badge, project `12955`; `.bestpractices.json` remains the conservative evidence record |
|
|
84
89
|
|
|
85
90
|
## Quick Start
|
|
86
91
|
|
|
@@ -201,6 +206,16 @@ If `--base` cannot be resolved, `pr-risk` emits `pr-risk.diff-unavailable` inste
|
|
|
201
206
|
|
|
202
207
|
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.
|
|
203
208
|
|
|
209
|
+
## Repository Trust Hardening
|
|
210
|
+
|
|
211
|
+
See [docs/repository-trust-hardening.md](docs/repository-trust-hardening.md) for the public repository controls behind this release line: strict branch protection, required CI checks, Dependabot for npm and GitHub Actions, CodeQL SAST, fast-check fuzz/property tests, signed GitHub release assets backed by npm trusted publishing provenance, private vulnerability reporting, secret scanning, and push protection.
|
|
212
|
+
|
|
213
|
+
The latest GitHub releases mirror the npm package tarball and attach `*.tgz.sigstore.json` plus `*.tgz.intoto.jsonl` provenance assets. These assets are generated from npm provenance, with the tarball digest checked against the npm registry metadata before upload.
|
|
214
|
+
|
|
215
|
+
The current Scorecard improvement track focuses on real controls, not cosmetic score gaming: stricter review gates, detectable fuzzing, and the OpenSSF Best Practices Badge process. Some Scorecard items, such as repository age, contributor diversity, and reviewed PR history, improve only through time and normal public maintenance.
|
|
216
|
+
|
|
217
|
+
The repository now has an [OpenSSF Best Practices passing badge](https://www.bestpractices.dev/projects/12955). [.bestpractices.json](.bestpractices.json) remains the conservative evidence record for the public project entry. `dynamic_analysis_enable_assertions` is still intentionally marked unmet until runtime assertion coverage is broader than the current test, property, and fuzz assertions.
|
|
218
|
+
|
|
204
219
|
## Stripe Webhook Replay
|
|
205
220
|
|
|
206
221
|
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.
|
|
@@ -225,6 +240,8 @@ The hosted staging deployment planner is documented in [docs/hosted-staging-depl
|
|
|
225
240
|
|
|
226
241
|
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.
|
|
227
242
|
|
|
243
|
+
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 deployed ingress has the required Cloudflare credential bindings configured. It deliberately queues compact pull request identity records only; scan worker execution, GitHub installation-token exchange, PR diff fetching, and Check Run publishing remain blocked until their deployed evidence passes the hosted operational release gate.
|
|
244
|
+
|
|
228
245
|
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.
|
|
229
246
|
|
|
230
247
|
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.
|
|
@@ -271,7 +288,7 @@ Use `suppressions` for narrower false-positive handling when one rule is noisy o
|
|
|
271
288
|
|
|
272
289
|
## GitHub Action
|
|
273
290
|
|
|
274
|
-
The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.
|
|
291
|
+
The repo includes a composite Action. Use `v0` for the latest compatible pre-1.0 Action, a specific release tag such as `v0.25.0` for controlled upgrades, or pin a reviewed commit SHA for stricter supply-chain control:
|
|
275
292
|
|
|
276
293
|
```yaml
|
|
277
294
|
name: ai-saas-guard
|
|
@@ -388,6 +405,8 @@ node dist/cli.js scan --root .
|
|
|
388
405
|
|
|
389
406
|
Before publishing a CLI update, GitHub Action update, npm package, plugin, or public repository change, follow [docs/release-quality-knowledge-base.md](docs/release-quality-knowledge-base.md).
|
|
390
407
|
|
|
408
|
+
Contribution expectations are documented in [CONTRIBUTING.md](CONTRIBUTING.md), including pull request process, tests, rule-design requirements, release gate evidence, and public-safety constraints.
|
|
409
|
+
|
|
391
410
|
## Roadmap
|
|
392
411
|
|
|
393
412
|
Open-source core:
|
package/README.zh-CN.md
CHANGED
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
<a href="README.md">English README</a> | 中文
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
|
+
<p align="center">
|
|
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
|
+
<a href="https://www.bestpractices.dev/projects/12955"><img alt="OpenSSF Best Practices" src="https://www.bestpractices.dev/projects/12955/badge"></a>
|
|
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>
|
|
25
|
+
</p>
|
|
26
|
+
|
|
19
27
|
---
|
|
20
28
|
|
|
21
29
|
## 它解决什么问题
|
|
@@ -55,7 +63,7 @@ AI 能很快把一个 SaaS 从想法做成可运行的产品。真正难的是
|
|
|
55
63
|
|
|
56
64
|
这个仓库是公开 GitHub 仓库。
|
|
57
65
|
|
|
58
|
-
CLI 已发布到 npm:`ai-saas-guard@0.
|
|
66
|
+
CLI 已发布到 npm:`ai-saas-guard@0.25.0`。GitHub Action 支持 `v0` 浮动标签,也支持固定版本标签,例如 `v0.25.0`。
|
|
59
67
|
|
|
60
68
|
| 模块 | 状态 |
|
|
61
69
|
| --- | --- |
|
|
@@ -66,14 +74,18 @@ CLI 已发布到 npm:`ai-saas-guard@0.23.0`。GitHub Action 支持 `v0` 浮动
|
|
|
66
74
|
| Markdown PR summary | 已可用 |
|
|
67
75
|
| GitHub Action | 已可用 |
|
|
68
76
|
| 项目配置 | `.ai-saas-guard.json` 支持规则开关、severity 覆盖和 fail threshold |
|
|
69
|
-
| 当前版本 | `0.
|
|
70
|
-
| Action 标签 | `v0.
|
|
77
|
+
| 当前版本 | `0.25.0` |
|
|
78
|
+
| Action 标签 | `v0.25.0`、`v0` |
|
|
71
79
|
| npm 发布 | GitHub Actions Trusted Publisher/OIDC,无需长期 npm token |
|
|
72
|
-
|
|
|
80
|
+
| 仓库可信度加固 | 严格 branch protection、Dependabot、CodeQL、fast-check fuzzing、signed release provenance assets、private vulnerability reporting、secret scanning 和 push protection |
|
|
81
|
+
| 运行时加固 | 单文件和总扫描文本预算、markdown evidence 转义、1 MiB hosted webhook payload 上限、更严格的 hosted deployment 阻断 |
|
|
73
82
|
| Hosted production adapters | GitHub App JWT 签名、installation-token 请求规划、有边界的 worker 执行和终态 cleanup 规划 |
|
|
74
83
|
| Hosted app skeleton | Node/container HTTP ingress、health route、worker tick、in-memory provider adapters 和 deployment plan 校验 |
|
|
75
84
|
| Hosted staging deployment planner | provider binding、staging release-gate evidence、Node/container deployment 组合和 GitHub App promotion gating |
|
|
76
85
|
| Hosted staging harness | 本地 file-backed webhook replay、queue/report/Check Run artifact、worker cleanup 校验和 release-gate evidence fixture |
|
|
86
|
+
| Cloudflare hosted ingress | 已部署到 `https://ai-saas-guard-hosted.zr9959.workers.dev`,用于签名 GitHub webhook intake 和 KV 排队;scan worker 和 Check Run 仍然受 release gate 阻断 |
|
|
87
|
+
| Hosted GitHub App staging | 私有 App `ai-saas-guard-hosted`(`3834787`)已安装到 `zr9959/ai-saas-guard`,权限为 contents read、pull requests read、metadata read、checks write |
|
|
88
|
+
| OpenSSF Best Practices | 已获得 passing badge,项目 `12955`;`.bestpractices.json` 继续作为保守证据记录 |
|
|
77
89
|
|
|
78
90
|
## 快速开始
|
|
79
91
|
|
|
@@ -134,6 +146,16 @@ node dist/cli.js scan --root /path/to/your-saas
|
|
|
134
146
|
|
|
135
147
|
完整规则请看 [docs/rules.md](docs/rules.md)。
|
|
136
148
|
|
|
149
|
+
## 仓库可信度加固
|
|
150
|
+
|
|
151
|
+
公开仓库的维护和发布控制见 [docs/repository-trust-hardening.md](docs/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。
|
|
152
|
+
|
|
153
|
+
最新 GitHub releases 会镜像 npm package tarball,并附带 `*.tgz.sigstore.json` 和 `*.tgz.intoto.jsonl` provenance assets。上传前会用 npm registry metadata 校验 tarball digest,并使用 npm provenance 作为来源。
|
|
154
|
+
|
|
155
|
+
当前 Scorecard 提升路线优先做真实控制,不做表面刷分:更严格的 review gate、可被检测到的 fuzzing、以及 OpenSSF Best Practices Badge 流程。仓库年龄、贡献者多样性、已 review 的 PR 历史这些分数只能随着真实维护逐步提升。
|
|
156
|
+
|
|
157
|
+
仓库现在已经获得 [OpenSSF Best Practices passing badge](https://www.bestpractices.dev/projects/12955)。[.bestpractices.json](.bestpractices.json) 继续作为公开项目条目的保守证据记录。`dynamic_analysis_enable_assertions` 仍然谨慎标为 unmet,直到运行时断言覆盖面超过当前测试、property 和 fuzz assertions。
|
|
158
|
+
|
|
137
159
|
## PR 风险分流
|
|
138
160
|
|
|
139
161
|
`scan` 可以扫整个仓库,但这个项目更锋利的入口是 PR review。
|
|
@@ -244,7 +266,7 @@ jobs:
|
|
|
244
266
|
|
|
245
267
|
## Hosted GitHub App 设计
|
|
246
268
|
|
|
247
|
-
当前仓库已经包含未来 Hosted GitHub App
|
|
269
|
+
当前仓库已经包含未来 Hosted GitHub App 的设计文档、纯契约测试,以及第一个真实 Cloudflare hosted ingress。私有 staging GitHub App `ai-saas-guard-hosted` 已安装到 `zr9959/ai-saas-guard`,Cloudflare 已配置所需的云端凭据绑定。这个 ingress 已经能接收签名 webhook 并写入 KV 队列,但还不是完整自动扫描服务。
|
|
248
270
|
|
|
249
271
|
相关文档:
|
|
250
272
|
|
|
@@ -273,6 +295,7 @@ jobs:
|
|
|
273
295
|
- 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 服务
|
|
274
296
|
- 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 服务
|
|
275
297
|
- 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 服务
|
|
298
|
+
- Cloudflare hosted ingress:`hosted/cloudflare-worker` 已部署到 `https://ai-saas-guard-hosted.zr9959.workers.dev`,提供 `/healthz`、`/github/app/manifest-callback` 和签名 `/github/webhook` intake,并只把 compact pull request identity 写入 Cloudflare KV;staging GitHub App ID 为 `3834787`,installation ID 为 `135085075`;GitHub installation-token exchange、PR diff fetching、scan worker 和 Check Run publishing 仍然需要通过 hosted operational release gate
|
|
276
299
|
- webhook event parser
|
|
277
300
|
- check-run summary renderer
|
|
278
301
|
- Check Run publication planner:要求 repository `checks: write`,只从 compact report 生成有长度上限的 Check Run payload,包含 review categories、优先 review 文件、verification steps 和本地 CLI 复现命令;MVP 不发 PR comment
|
|
@@ -311,6 +334,8 @@ node dist/cli.js scan --root .
|
|
|
311
334
|
|
|
312
335
|
以后更新英文 `README.md` 时,也要同步检查并更新本中文 `README.zh-CN.md`。
|
|
313
336
|
|
|
337
|
+
贡献要求见 [CONTRIBUTING.md](CONTRIBUTING.md),里面说明了 PR 流程、测试要求、规则设计、release gate evidence 和公开安全边界。
|
|
338
|
+
|
|
314
339
|
## 安全报告
|
|
315
340
|
|
|
316
341
|
报告漏洞前请阅读 [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.25.0` or a reviewed commit SHA when reproducibility is more important than automatic minor updates.
|
|
6
6
|
|
|
7
7
|
## PR Summary
|
|
8
8
|
|
|
@@ -78,6 +78,15 @@ When `readyToCreateGitHubApp` is true:
|
|
|
78
78
|
|
|
79
79
|
## Current Status
|
|
80
80
|
|
|
81
|
-
The repository can now produce and validate the deployment plan,
|
|
81
|
+
The repository can now produce and validate the deployment plan, and a private staging GitHub App exists for the first live hosted ingress:
|
|
82
|
+
|
|
83
|
+
- App slug: `ai-saas-guard-hosted`
|
|
84
|
+
- App ID: `3834787`
|
|
85
|
+
- Installation ID: `135085075`
|
|
86
|
+
- Installed repository: `zr9959/ai-saas-guard`
|
|
87
|
+
- Webhook URL: `https://ai-saas-guard-hosted.zr9959.workers.dev/github/webhook`
|
|
88
|
+
- Secret storage: Cloudflare Worker secrets for `WEBHOOK_SECRET` and `GITHUB_APP_PRIVATE_KEY`
|
|
89
|
+
|
|
90
|
+
This is still an ingress-only staging deployment. It verifies signatures and queues compact pull request identity records, but it does not fetch PR diffs, exchange installation tokens for worker access, run scans, or publish Check Runs yet.
|
|
82
91
|
|
|
83
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.
|
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.25.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.25.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.25.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
|
@@ -63,12 +63,15 @@ Implemented surfaces:
|
|
|
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
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
|
|
66
|
+
- live Cloudflare hosted ingress at `https://ai-saas-guard-hosted.zr9959.workers.dev` with `/healthz`, `/github/app/manifest-callback`, signed `/github/webhook` intake, Cloudflare KV storage, private staging GitHub App `ai-saas-guard-hosted` (`3834787`) installed on `zr9959/ai-saas-guard`, and explicit `shouldCreateCheckRun: false` until scan worker and Check Run publishing are wired
|
|
66
67
|
- resource caps for repository text collection, including per-file, total-file, and total-byte scan budgets to reduce worst-case memory use
|
|
67
68
|
- 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
|
|
68
69
|
- implementation-ready hosted GitHub App permission contract for required permissions, optional PR comment permissions, selected repository installation, and out-of-scope broad permissions
|
|
69
70
|
- 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
|
|
70
71
|
- GitHub issue templates for bug reports, false positives, false negatives, rule requests, and public-safe security reports
|
|
71
72
|
- CODEOWNERS for source, tests, docs, workflows, Action, and package metadata
|
|
73
|
+
- repository trust hardening with strict `main` branch protection, required CI status checks, fast-check fuzzing, signed GitHub release assets backed by npm trusted publishing provenance, Dependabot for npm and GitHub Actions, CodeQL, private vulnerability reporting, secret scanning, and push protection
|
|
74
|
+
- OpenSSF Best Practices passing badge at https://www.bestpractices.dev/projects/12955, with `.bestpractices.json` for conservative repository-backed answer proposals and `CONTRIBUTING.md` for pull request process, tests, rule-design requirements, release gate evidence, and public-safety constraints
|
|
72
75
|
- JSON output
|
|
73
76
|
- SARIF output
|
|
74
77
|
- composite GitHub Action wrapper
|
|
@@ -132,12 +135,32 @@ CI:
|
|
|
132
135
|
- Workflow: `.github/workflows/ci.yml`
|
|
133
136
|
- Runs on pull requests and pushes to `main`
|
|
134
137
|
- Uses `permissions: contents: read`
|
|
135
|
-
-
|
|
138
|
+
- Static workflow checks: `actionlint` and `zizmor`
|
|
139
|
+
- Code scanning workflow: `.github/workflows/codeql.yml`
|
|
140
|
+
- Fuzz/property tests: `npm run test:fuzz` with `fast-check`
|
|
141
|
+
- Dependabot config: `.github/dependabot.yml` with weekly schedules, bounded PR volume, and cooldown windows
|
|
142
|
+
- Latest verified run for the repository trust hardening release must succeed before publishing
|
|
143
|
+
|
|
144
|
+
Hosted staging:
|
|
145
|
+
|
|
146
|
+
- Cloudflare Worker URL: https://ai-saas-guard-hosted.zr9959.workers.dev
|
|
147
|
+
- Cloudflare KV binding: `HOSTED_EVENTS`
|
|
148
|
+
- GitHub App: `ai-saas-guard-hosted`
|
|
149
|
+
- GitHub App ID: `3834787`
|
|
150
|
+
- GitHub App installation ID: `135085075`
|
|
151
|
+
- Installed repository: `zr9959/ai-saas-guard`
|
|
152
|
+
- Current hosted mode: signed webhook ingress and compact queueing only
|
|
153
|
+
- Not yet complete: GitHub App installation-token exchange, PR diff fetching, scan worker execution, Check Run publishing, monitoring evidence, rollback evidence, and incident-response evidence
|
|
154
|
+
|
|
155
|
+
OpenSSF Best Practices:
|
|
156
|
+
|
|
157
|
+
- Project: https://www.bestpractices.dev/projects/12955
|
|
158
|
+
- Badge level: passing
|
|
136
159
|
|
|
137
160
|
Publishing:
|
|
138
161
|
|
|
139
162
|
- npm package: `ai-saas-guard`
|
|
140
|
-
- Current release line: `v0.
|
|
163
|
+
- Current release line: `v0.25.0`
|
|
141
164
|
- Publish workflow: `.github/workflows/npm-publish.yml`
|
|
142
165
|
- Trusted Publisher: GitHub Actions for `zr9959/ai-saas-guard`, workflow `npm-publish.yml`
|
|
143
166
|
- Long-lived npm publish tokens should not be required.
|
|
@@ -170,7 +170,7 @@ P2:
|
|
|
170
170
|
|
|
171
171
|
- Use GitHub Projects for a public roadmap.
|
|
172
172
|
- Add OpenSSF Scorecard badge once score is acceptable.
|
|
173
|
-
-
|
|
173
|
+
- Keep the OpenSSF Best Practices Badge evidence current after public process, README, or release-gate changes.
|
|
174
174
|
|
|
175
175
|
## GitHub Actions Security
|
|
176
176
|
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
- administrator enforcement
|
|
14
|
+
- stale review dismissal
|
|
15
|
+
- CODEOWNERS review
|
|
16
|
+
- last-push approval
|
|
17
|
+
- two approving reviews
|
|
18
|
+
- linear history
|
|
19
|
+
- force pushes disabled
|
|
20
|
+
- branch deletion disabled
|
|
21
|
+
|
|
22
|
+
Required status checks:
|
|
23
|
+
|
|
24
|
+
- `test`
|
|
25
|
+
- `fuzz`
|
|
26
|
+
- `actionlint`
|
|
27
|
+
- `zizmor`
|
|
28
|
+
- `codeql`
|
|
29
|
+
|
|
30
|
+
Maintainer admin bypass is enforced for normal branch updates. Repository administrators can still update protection settings through GitHub admin APIs if emergency recovery is needed.
|
|
31
|
+
|
|
32
|
+
## Dependency Updates
|
|
33
|
+
|
|
34
|
+
Dependabot is configured in `.github/dependabot.yml`.
|
|
35
|
+
|
|
36
|
+
It covers:
|
|
37
|
+
|
|
38
|
+
- npm dependencies
|
|
39
|
+
- GitHub Actions
|
|
40
|
+
|
|
41
|
+
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.
|
|
42
|
+
|
|
43
|
+
Dependabot security updates and vulnerability alerts are enabled in repository settings.
|
|
44
|
+
|
|
45
|
+
## CodeQL
|
|
46
|
+
|
|
47
|
+
CodeQL is configured in `.github/workflows/codeql.yml`.
|
|
48
|
+
|
|
49
|
+
The workflow:
|
|
50
|
+
|
|
51
|
+
- runs on pull requests
|
|
52
|
+
- runs on pushes to `main`
|
|
53
|
+
- runs on a weekly schedule
|
|
54
|
+
- analyzes JavaScript and TypeScript
|
|
55
|
+
- uses `build-mode: none`
|
|
56
|
+
- uses least-privilege permissions: repository contents read, Actions metadata read, and security event upload
|
|
57
|
+
- pins the CodeQL Action by commit SHA
|
|
58
|
+
|
|
59
|
+
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.
|
|
60
|
+
|
|
61
|
+
## Fuzzing
|
|
62
|
+
|
|
63
|
+
The repository runs Scorecard-detectable fuzzing with `fast-check`.
|
|
64
|
+
|
|
65
|
+
The fuzz tests cover:
|
|
66
|
+
|
|
67
|
+
- markdown report escaping for attacker-controlled evidence
|
|
68
|
+
- SARIF serialization for arbitrary finding text
|
|
69
|
+
- generated secret redaction paths
|
|
70
|
+
|
|
71
|
+
The dedicated `fuzz` CI job runs `npm run test:fuzz`. The regular `test` job also includes `tests/fuzz.test.js` because it runs the full Node test suite.
|
|
72
|
+
|
|
73
|
+
## Signed Release Assets
|
|
74
|
+
|
|
75
|
+
GitHub releases mirror the published npm package tarball and attach the npm trusted publishing provenance used for that release.
|
|
76
|
+
|
|
77
|
+
Each release should include:
|
|
78
|
+
|
|
79
|
+
- `ai-saas-guard-<version>.tgz`
|
|
80
|
+
- `ai-saas-guard-<version>.tgz.sigstore.json`
|
|
81
|
+
- `ai-saas-guard-<version>.tgz.intoto.jsonl`
|
|
82
|
+
|
|
83
|
+
Before upload, the tarball SHA-512 digest must match npm registry metadata, and the SLSA subject digest in the npm provenance must match the same tarball digest. The `sigstore.json` asset keeps the npm Sigstore bundle for independent verification. The `intoto.jsonl` asset keeps the DSSE in-toto envelope that OpenSSF Scorecard and other release-integrity tooling can detect.
|
|
84
|
+
|
|
85
|
+
## Vulnerability Intake
|
|
86
|
+
|
|
87
|
+
The repository has:
|
|
88
|
+
|
|
89
|
+
- `SECURITY.md`
|
|
90
|
+
- private vulnerability reporting enabled
|
|
91
|
+
- secret scanning enabled
|
|
92
|
+
- push protection enabled
|
|
93
|
+
|
|
94
|
+
Public issues should not include real credentials, customer data, private source code, or production URLs.
|
|
95
|
+
|
|
96
|
+
## Release Impact
|
|
97
|
+
|
|
98
|
+
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:
|
|
99
|
+
|
|
100
|
+
- local tests
|
|
101
|
+
- GitHub CI
|
|
102
|
+
- `actionlint`
|
|
103
|
+
- `zizmor`
|
|
104
|
+
- self-scan JSON and SARIF
|
|
105
|
+
- dependency audit
|
|
106
|
+
- fuzz/property tests
|
|
107
|
+
- signed release asset digest and npm provenance checks
|
|
108
|
+
- npm package inspection
|
|
109
|
+
- packaged CLI smoke test
|
|
110
|
+
|
|
111
|
+
## OpenSSF Best Practices Badge
|
|
112
|
+
|
|
113
|
+
The OpenSSF Best Practices Badge is tracked as a separate public trust signal. The badge must be earned through the OpenSSF Best Practices web application and API; it cannot be truthfully completed by repository files alone.
|
|
114
|
+
|
|
115
|
+
The repository has earned the OpenSSF Best Practices passing badge:
|
|
116
|
+
|
|
117
|
+
- Project page: https://www.bestpractices.dev/projects/12955
|
|
118
|
+
- Badge API: https://www.bestpractices.dev/projects/12955/badge.json
|
|
119
|
+
- Passing achieved on 2026-05-24.
|
|
120
|
+
|
|
121
|
+
The repository also includes `.bestpractices.json` with conservative proposed answers backed by public repository evidence. The file remains the evidence record for future maintainer review, but the public badge state is the OpenSSF project record above.
|
|
122
|
+
|
|
123
|
+
Current badge evidence:
|
|
124
|
+
|
|
125
|
+
- `CONTRIBUTING.md` documents the pull request process, test expectations, rule-design requirements, release gate evidence, and public-safety constraints.
|
|
126
|
+
- `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.
|
|
127
|
+
- `.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.
|
|
128
|
+
- `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.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Cloudflare Hosted Ingress
|
|
2
|
+
|
|
3
|
+
This directory contains the first live hosted ingress for `ai-saas-guard`.
|
|
4
|
+
|
|
5
|
+
It is intentionally narrow:
|
|
6
|
+
|
|
7
|
+
- `GET /healthz` returns public-safe service health.
|
|
8
|
+
- `GET /github/app/manifest-callback` acknowledges the GitHub App manifest redirect without storing the one-time code.
|
|
9
|
+
- `POST /github/webhook` verifies GitHub `sha256` webhook signatures before JSON parsing or storage.
|
|
10
|
+
- Requests over 1 MiB are rejected before JSON parsing or KV writes.
|
|
11
|
+
- Signed `pull_request` events are reduced to trusted GitHub identity fields and stored in Cloudflare KV.
|
|
12
|
+
- Duplicate GitHub delivery IDs are accepted idempotently.
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
15
|
+
This Worker is a real hosted ingress, not yet the complete scan worker. `shouldCreateCheckRun` stays `false` until GitHub App installation-token exchange, PR diff fetching, PR-risk classification, and Check Run publishing are wired and verified against deployed infrastructure.
|
|
16
|
+
|
|
17
|
+
## Required Cloudflare Bindings
|
|
18
|
+
|
|
19
|
+
- `HOSTED_EVENTS`: Cloudflare KV namespace for compact delivery and queued scan records.
|
|
20
|
+
- `WEBHOOK_SECRET`: Worker secret matching the GitHub App webhook secret.
|
|
21
|
+
- `GITHUB_APP_PRIVATE_KEY`: Worker secret for the staging GitHub App private key. It is stored for the next Check Run publishing slice and is not used by this ingress-only Worker yet.
|
|
22
|
+
- `SCANNER_VERSION`: public version string, currently `0.25.0`.
|
|
23
|
+
- `GITHUB_APP_ID`, `GITHUB_APP_SLUG`, `GITHUB_APP_INSTALLATION_ID`: public staging identifiers for the private GitHub App installation.
|
|
24
|
+
|
|
25
|
+
## Deployment
|
|
26
|
+
|
|
27
|
+
Use `wrangler` from this directory:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx wrangler kv namespace create HOSTED_EVENTS
|
|
31
|
+
npx wrangler secret put WEBHOOK_SECRET
|
|
32
|
+
npx wrangler secret put GITHUB_APP_PRIVATE_KEY
|
|
33
|
+
npx wrangler deploy
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
After creating the KV namespace, replace the placeholder namespace ID in `wrangler.jsonc`.
|
|
37
|
+
|
|
38
|
+
Current public staging endpoint:
|
|
39
|
+
|
|
40
|
+
- Worker URL: `https://ai-saas-guard-hosted.zr9959.workers.dev`
|
|
41
|
+
- KV namespace binding: `HOSTED_EVENTS`
|
|
42
|
+
- KV namespace ID: `fa5344fbd7944de6a776bf8731d58460`
|
|
43
|
+
- GitHub App slug: `ai-saas-guard-hosted`
|
|
44
|
+
- GitHub App ID: `3834787`
|
|
45
|
+
- GitHub App installation ID: `135085075`
|
|
46
|
+
- Installed repository: `zr9959/ai-saas-guard`
|
|
47
|
+
- Mode: signed webhook ingress and compact queueing only
|
|
48
|
+
|
|
49
|
+
## Release Boundary
|
|
50
|
+
|
|
51
|
+
Do not expose this as the full product. The hosted operational release gate still requires deployed evidence for Check Run publication, worker cleanup, monitoring, rollback, incident response, dependency and deployment artifact scanning, and GitHub App installation behavior.
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
export const HOSTED_WORKER_PRIVACY = {
|
|
2
|
+
includesRawWebhookPayload: false,
|
|
3
|
+
includesUntrustedPrText: false,
|
|
4
|
+
includesRawSource: false,
|
|
5
|
+
includesRawDiffs: false,
|
|
6
|
+
includesSecrets: false,
|
|
7
|
+
includesCustomerPayloads: false,
|
|
8
|
+
includesPrivateCheckoutPath: false,
|
|
9
|
+
includesInstallationToken: false
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const ALLOWED_PULL_REQUEST_ACTIONS = new Set(["opened", "reopened", "synchronize", "ready_for_review"]);
|
|
13
|
+
const JSON_HEADERS = {
|
|
14
|
+
"content-type": "application/json; charset=utf-8"
|
|
15
|
+
};
|
|
16
|
+
const EVENT_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
17
|
+
export const MAX_WEBHOOK_PAYLOAD_BYTES = 1024 * 1024;
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
async fetch(request, env) {
|
|
21
|
+
const url = new URL(request.url);
|
|
22
|
+
|
|
23
|
+
if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/healthz")) {
|
|
24
|
+
return jsonResponse(200, createHostedWorkerHealth());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (request.method === "GET" && url.pathname === "/github/app/manifest-callback") {
|
|
28
|
+
return jsonResponse(200, createGitHubAppManifestCallback(url));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (url.pathname !== "/github/webhook") {
|
|
32
|
+
return jsonResponse(404, {
|
|
33
|
+
accepted: false,
|
|
34
|
+
reason: "not_found",
|
|
35
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (request.method !== "POST") {
|
|
40
|
+
return jsonResponse(405, {
|
|
41
|
+
accepted: false,
|
|
42
|
+
reason: "method_not_allowed",
|
|
43
|
+
allowedMethods: ["POST"],
|
|
44
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!env?.WEBHOOK_SECRET || !env?.HOSTED_EVENTS) {
|
|
49
|
+
return jsonResponse(503, {
|
|
50
|
+
accepted: false,
|
|
51
|
+
stage: "configuration",
|
|
52
|
+
reason: "missing_worker_binding",
|
|
53
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isPayloadTooLarge(request.headers.get("content-length"))) {
|
|
58
|
+
return jsonResponse(413, {
|
|
59
|
+
accepted: false,
|
|
60
|
+
stage: "payload",
|
|
61
|
+
reason: "payload_too_large",
|
|
62
|
+
maxBytes: MAX_WEBHOOK_PAYLOAD_BYTES,
|
|
63
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const payload = await request.text();
|
|
68
|
+
if (new TextEncoder().encode(payload).byteLength > MAX_WEBHOOK_PAYLOAD_BYTES) {
|
|
69
|
+
return jsonResponse(413, {
|
|
70
|
+
accepted: false,
|
|
71
|
+
stage: "payload",
|
|
72
|
+
reason: "payload_too_large",
|
|
73
|
+
maxBytes: MAX_WEBHOOK_PAYLOAD_BYTES,
|
|
74
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const signatureHeader = request.headers.get("x-hub-signature-256") ?? "";
|
|
79
|
+
const deliveryId = request.headers.get("x-github-delivery") ?? "";
|
|
80
|
+
const eventName = request.headers.get("x-github-event") ?? "";
|
|
81
|
+
|
|
82
|
+
const signatureAccepted = await verifyGitHubWebhookSignature({
|
|
83
|
+
payload,
|
|
84
|
+
signatureHeader,
|
|
85
|
+
secret: env.WEBHOOK_SECRET
|
|
86
|
+
});
|
|
87
|
+
if (!signatureAccepted) {
|
|
88
|
+
return jsonResponse(400, {
|
|
89
|
+
accepted: false,
|
|
90
|
+
stage: "signature",
|
|
91
|
+
reason: "invalid_signature",
|
|
92
|
+
deliveryId: deliveryId || undefined,
|
|
93
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!deliveryId) {
|
|
98
|
+
return jsonResponse(400, {
|
|
99
|
+
accepted: false,
|
|
100
|
+
stage: "event",
|
|
101
|
+
reason: "missing_delivery_id",
|
|
102
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const deliveryKey = `delivery:${deliveryId}`;
|
|
107
|
+
const existingDelivery = await env.HOSTED_EVENTS.get(deliveryKey);
|
|
108
|
+
if (existingDelivery) {
|
|
109
|
+
return jsonResponse(202, {
|
|
110
|
+
accepted: true,
|
|
111
|
+
stage: "duplicate_delivery",
|
|
112
|
+
deliveryId,
|
|
113
|
+
shouldCreateCheckRun: false,
|
|
114
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (eventName !== "pull_request") {
|
|
119
|
+
await storeJson(env.HOSTED_EVENTS, deliveryKey, {
|
|
120
|
+
deliveryId,
|
|
121
|
+
eventName,
|
|
122
|
+
accepted: false,
|
|
123
|
+
reason: "unsupported_event",
|
|
124
|
+
receivedAt: new Date().toISOString()
|
|
125
|
+
});
|
|
126
|
+
return jsonResponse(202, {
|
|
127
|
+
accepted: true,
|
|
128
|
+
stage: "ignored",
|
|
129
|
+
reason: "unsupported_event",
|
|
130
|
+
deliveryId,
|
|
131
|
+
shouldCreateCheckRun: false,
|
|
132
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let parsedPayload;
|
|
137
|
+
try {
|
|
138
|
+
parsedPayload = JSON.parse(payload);
|
|
139
|
+
} catch {
|
|
140
|
+
return jsonResponse(400, {
|
|
141
|
+
accepted: false,
|
|
142
|
+
stage: "payload",
|
|
143
|
+
reason: "invalid_json",
|
|
144
|
+
deliveryId,
|
|
145
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const identity = parsePullRequestWebhookIdentity(parsedPayload);
|
|
150
|
+
if (!identity) {
|
|
151
|
+
return jsonResponse(400, {
|
|
152
|
+
accepted: false,
|
|
153
|
+
stage: "event",
|
|
154
|
+
reason: "missing_required_identity",
|
|
155
|
+
deliveryId,
|
|
156
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (identity.draft || !ALLOWED_PULL_REQUEST_ACTIONS.has(identity.action)) {
|
|
161
|
+
await storeJson(env.HOSTED_EVENTS, deliveryKey, {
|
|
162
|
+
deliveryId,
|
|
163
|
+
eventName,
|
|
164
|
+
accepted: false,
|
|
165
|
+
reason: identity.draft ? "draft_pull_request" : "unsupported_pull_request_action",
|
|
166
|
+
identity,
|
|
167
|
+
receivedAt: new Date().toISOString()
|
|
168
|
+
});
|
|
169
|
+
return jsonResponse(202, {
|
|
170
|
+
accepted: true,
|
|
171
|
+
stage: "ignored",
|
|
172
|
+
reason: identity.draft ? "draft_pull_request" : "unsupported_pull_request_action",
|
|
173
|
+
deliveryId,
|
|
174
|
+
shouldCreateCheckRun: false,
|
|
175
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const scannerVersion = env.SCANNER_VERSION || "unknown";
|
|
180
|
+
const scanKey = [
|
|
181
|
+
"scan",
|
|
182
|
+
identity.installationId,
|
|
183
|
+
identity.repositoryId,
|
|
184
|
+
identity.pullRequestNumber,
|
|
185
|
+
identity.headSha,
|
|
186
|
+
scannerVersion
|
|
187
|
+
].join(":");
|
|
188
|
+
|
|
189
|
+
await storeJson(env.HOSTED_EVENTS, deliveryKey, {
|
|
190
|
+
deliveryId,
|
|
191
|
+
eventName,
|
|
192
|
+
accepted: true,
|
|
193
|
+
scanKey,
|
|
194
|
+
identity,
|
|
195
|
+
receivedAt: new Date().toISOString()
|
|
196
|
+
});
|
|
197
|
+
await storeJson(env.HOSTED_EVENTS, scanKey, {
|
|
198
|
+
key: scanKey,
|
|
199
|
+
status: "queued",
|
|
200
|
+
identity,
|
|
201
|
+
scannerVersion,
|
|
202
|
+
deliveryIds: [deliveryId],
|
|
203
|
+
createdAt: new Date().toISOString(),
|
|
204
|
+
updatedAt: new Date().toISOString(),
|
|
205
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return jsonResponse(202, {
|
|
209
|
+
accepted: true,
|
|
210
|
+
stage: "queued",
|
|
211
|
+
deliveryId,
|
|
212
|
+
scanKey,
|
|
213
|
+
shouldCreateCheckRun: false,
|
|
214
|
+
worker: "not_configured",
|
|
215
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export function createHostedWorkerHealth() {
|
|
221
|
+
return {
|
|
222
|
+
ok: true,
|
|
223
|
+
service: "ai-saas-guard-hosted",
|
|
224
|
+
mode: "webhook-ingress",
|
|
225
|
+
routes: ["/healthz", "/github/app/manifest-callback", "/github/webhook"],
|
|
226
|
+
storage: "cloudflare_kv",
|
|
227
|
+
checkRunPublisher: "not_configured",
|
|
228
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function createGitHubAppManifestCallback(url) {
|
|
233
|
+
return {
|
|
234
|
+
ok: true,
|
|
235
|
+
service: "ai-saas-guard-hosted",
|
|
236
|
+
stage: "github_app_manifest_callback",
|
|
237
|
+
manifestCodeReceived: url.searchParams.has("code"),
|
|
238
|
+
storesManifestCode: false,
|
|
239
|
+
nextStep: "exchange_code_server_side",
|
|
240
|
+
privacy: HOSTED_WORKER_PRIVACY
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function verifyGitHubWebhookSignature({ payload, signatureHeader, secret }) {
|
|
245
|
+
if (!payload || !signatureHeader || !secret || !signatureHeader.startsWith("sha256=")) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const expected = await hmacSha256Header(payload, secret);
|
|
250
|
+
return constantTimeEqual(expected, signatureHeader);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function parsePullRequestWebhookIdentity(payload) {
|
|
254
|
+
const installationId = integerValue(payload?.installation?.id);
|
|
255
|
+
const repositoryId = integerValue(payload?.repository?.id);
|
|
256
|
+
const repositoryFullName = stringValue(payload?.repository?.full_name);
|
|
257
|
+
const pullRequestNumber = integerValue(payload?.pull_request?.number);
|
|
258
|
+
const baseSha = shaValue(payload?.pull_request?.base?.sha);
|
|
259
|
+
const headSha = shaValue(payload?.pull_request?.head?.sha);
|
|
260
|
+
const action = stringValue(payload?.action);
|
|
261
|
+
|
|
262
|
+
if (
|
|
263
|
+
installationId === undefined ||
|
|
264
|
+
repositoryId === undefined ||
|
|
265
|
+
repositoryFullName === undefined ||
|
|
266
|
+
pullRequestNumber === undefined ||
|
|
267
|
+
baseSha === undefined ||
|
|
268
|
+
headSha === undefined ||
|
|
269
|
+
action === undefined
|
|
270
|
+
) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
action,
|
|
276
|
+
installationId,
|
|
277
|
+
repositoryId,
|
|
278
|
+
repositoryFullName,
|
|
279
|
+
repositoryPrivate: Boolean(payload?.repository?.private),
|
|
280
|
+
pullRequestNumber,
|
|
281
|
+
baseSha,
|
|
282
|
+
headSha,
|
|
283
|
+
draft: Boolean(payload?.pull_request?.draft)
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function hmacSha256Header(payload, secret) {
|
|
288
|
+
const encoder = new TextEncoder();
|
|
289
|
+
const key = await crypto.subtle.importKey(
|
|
290
|
+
"raw",
|
|
291
|
+
encoder.encode(secret),
|
|
292
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
293
|
+
false,
|
|
294
|
+
["sign"]
|
|
295
|
+
);
|
|
296
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
|
|
297
|
+
return `sha256=${bufferToHex(signature)}`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function constantTimeEqual(left, right) {
|
|
301
|
+
if (left.length !== right.length) return false;
|
|
302
|
+
|
|
303
|
+
let mismatch = 0;
|
|
304
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
305
|
+
mismatch |= left.charCodeAt(index) ^ right.charCodeAt(index);
|
|
306
|
+
}
|
|
307
|
+
return mismatch === 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function bufferToHex(buffer) {
|
|
311
|
+
return [...new Uint8Array(buffer)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function integerValue(value) {
|
|
315
|
+
return Number.isSafeInteger(value) ? value : undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function stringValue(value) {
|
|
319
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function shaValue(value) {
|
|
323
|
+
return typeof value === "string" && /^[a-f0-9]{40}$/i.test(value) ? value : undefined;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function isPayloadTooLarge(contentLength) {
|
|
327
|
+
if (contentLength === null) return false;
|
|
328
|
+
const parsed = Number(contentLength);
|
|
329
|
+
return Number.isFinite(parsed) && parsed > MAX_WEBHOOK_PAYLOAD_BYTES;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function storeJson(kv, key, value) {
|
|
333
|
+
await kv.put(key, JSON.stringify(value), { expirationTtl: EVENT_TTL_SECONDS });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function jsonResponse(status, body) {
|
|
337
|
+
return new Response(JSON.stringify(body), {
|
|
338
|
+
status,
|
|
339
|
+
headers: JSON_HEADERS
|
|
340
|
+
});
|
|
341
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://developers.cloudflare.com/workers/wrangler/config-schema.json",
|
|
3
|
+
"name": "ai-saas-guard-hosted",
|
|
4
|
+
"main": "src/index.js",
|
|
5
|
+
"compatibility_date": "2026-05-24",
|
|
6
|
+
"observability": {
|
|
7
|
+
"enabled": true
|
|
8
|
+
},
|
|
9
|
+
"vars": {
|
|
10
|
+
"SCANNER_VERSION": "0.25.0",
|
|
11
|
+
"GITHUB_APP_ID": "3834787",
|
|
12
|
+
"GITHUB_APP_SLUG": "ai-saas-guard-hosted",
|
|
13
|
+
"GITHUB_APP_INSTALLATION_ID": "135085075"
|
|
14
|
+
},
|
|
15
|
+
"kv_namespaces": [
|
|
16
|
+
{
|
|
17
|
+
"binding": "HOSTED_EVENTS",
|
|
18
|
+
"id": "fa5344fbd7944de6a776bf8731d58460"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-saas-guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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",
|
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
67
|
"build": "tsc -p tsconfig.json",
|
|
68
|
-
"test": "npm run build && node --test tests/*.test.mjs",
|
|
68
|
+
"test": "npm run build && node --test tests/*.test.mjs tests/*.test.js",
|
|
69
|
+
"test:fuzz": "npm run build && node --test tests/fuzz.test.js",
|
|
69
70
|
"check": "npm run test"
|
|
70
71
|
},
|
|
71
72
|
"engines": {
|
|
@@ -73,15 +74,19 @@
|
|
|
73
74
|
},
|
|
74
75
|
"files": [
|
|
75
76
|
"action.yml",
|
|
77
|
+
".bestpractices.json",
|
|
78
|
+
"CONTRIBUTING.md",
|
|
76
79
|
"docs",
|
|
77
80
|
"dist",
|
|
78
81
|
"examples",
|
|
82
|
+
"hosted",
|
|
79
83
|
"README.md",
|
|
80
84
|
"README.zh-CN.md"
|
|
81
85
|
],
|
|
82
86
|
"license": "MIT",
|
|
83
87
|
"devDependencies": {
|
|
84
88
|
"@types/node": "^20",
|
|
89
|
+
"fast-check": "^4.8.0",
|
|
85
90
|
"typescript": "^5"
|
|
86
91
|
}
|
|
87
92
|
}
|