@prodcycle/prodcycle 0.1.5
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/LICENSE +52 -0
- package/README.md +93 -0
- package/dist/api-client.d.ts +26 -0
- package/dist/api-client.js +53 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +110 -0
- package/dist/formatters/prompt.d.ts +1 -0
- package/dist/formatters/prompt.js +8 -0
- package/dist/formatters/sarif.d.ts +1 -0
- package/dist/formatters/sarif.js +9 -0
- package/dist/formatters/table.d.ts +1 -0
- package/dist/formatters/table.js +9 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +82 -0
- package/dist/utils/fs.d.ts +1 -0
- package/dist/utils/fs.js +83 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Copyright (c) 2025-2026 ProdCycle, Inc. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and associated documentation files (the "Software") are the
|
|
4
|
+
proprietary property of ProdCycle, Inc. and are protected by copyright law.
|
|
5
|
+
|
|
6
|
+
GRANT OF LICENSE
|
|
7
|
+
|
|
8
|
+
Subject to valid license key activation and the terms of your ProdCycle
|
|
9
|
+
subscription agreement, ProdCycle, Inc. grants you a limited, non-exclusive,
|
|
10
|
+
non-transferable, revocable license to use the Software solely for your
|
|
11
|
+
internal business purposes.
|
|
12
|
+
|
|
13
|
+
RESTRICTIONS
|
|
14
|
+
|
|
15
|
+
You may not, without the prior written consent of ProdCycle, Inc.:
|
|
16
|
+
|
|
17
|
+
1. Copy, modify, or create derivative works of the Software;
|
|
18
|
+
2. Distribute, sublicense, lease, rent, or lend the Software to any
|
|
19
|
+
third party;
|
|
20
|
+
3. Reverse engineer, decompile, or disassemble the Software;
|
|
21
|
+
4. Remove or alter any proprietary notices, labels, or marks on the
|
|
22
|
+
Software;
|
|
23
|
+
5. Use the Software to build a competing product or service.
|
|
24
|
+
|
|
25
|
+
COMPLIANCE POLICIES
|
|
26
|
+
|
|
27
|
+
The compliance policy definitions (Rego rules, Cedar policies, and framework
|
|
28
|
+
control mappings) included in the `policies/` and `frameworks/` directories
|
|
29
|
+
are provided as part of the Software and are subject to the same license
|
|
30
|
+
terms.
|
|
31
|
+
|
|
32
|
+
DISCLAIMER OF WARRANTIES
|
|
33
|
+
|
|
34
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
35
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
36
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
37
|
+
|
|
38
|
+
LIMITATION OF LIABILITY
|
|
39
|
+
|
|
40
|
+
IN NO EVENT SHALL PRODCYCLE, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
41
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
42
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
43
|
+
SOFTWARE.
|
|
44
|
+
|
|
45
|
+
TERMINATION
|
|
46
|
+
|
|
47
|
+
This license is effective until terminated. ProdCycle, Inc. may terminate this
|
|
48
|
+
license at any time if you fail to comply with any term of this agreement.
|
|
49
|
+
Upon termination, you must destroy all copies of the Software in your
|
|
50
|
+
possession.
|
|
51
|
+
|
|
52
|
+
For licensing inquiries, contact: licensing@prodcycle.com
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @prodcycle/prodcycle
|
|
2
|
+
|
|
3
|
+
Multi-framework policy-as-code compliance scanner for infrastructure and application code. Scans Terraform, Kubernetes, Docker, `.env`, and application source against SOC 2, HIPAA, and NIST CSF policies.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **3 compliance frameworks**: SOC 2, HIPAA, NIST CSF
|
|
8
|
+
- **Automated policy enforcement**: Server-side OPA/Rego and Cedar evaluation engines
|
|
9
|
+
- **Infrastructure scanning**: Terraform, Kubernetes manifests, Dockerfiles, `.env` files
|
|
10
|
+
- **Application code scanning**: TypeScript, Python, Go, Java, Ruby
|
|
11
|
+
- **CI/CD integration**: CLI with SARIF output for GitHub Code Scanning
|
|
12
|
+
- **Programmatic API**: Full TypeScript API for custom integrations
|
|
13
|
+
- **Self-remediation**: `gate()` function returns actionable remediation prompts
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @prodcycle/prodcycle
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### GitHub Packages (alternative)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
echo "@prodcycle:registry=https://npm.pkg.github.com" > .npmrc
|
|
25
|
+
npm login --scope=@prodcycle --registry=https://npm.pkg.github.com
|
|
26
|
+
npm install @prodcycle/prodcycle
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### CLI
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Scan current directory against SOC 2 and HIPAA
|
|
35
|
+
prodcycle . --framework soc2,hipaa
|
|
36
|
+
|
|
37
|
+
# Output as SARIF for GitHub Code Scanning
|
|
38
|
+
prodcycle . --framework soc2 --format sarif --output results.sarif
|
|
39
|
+
|
|
40
|
+
# Set severity threshold (only report HIGH and above)
|
|
41
|
+
prodcycle . --framework hipaa --severity-threshold high
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Programmatic API
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { scan, gate } from '@prodcycle/prodcycle';
|
|
48
|
+
|
|
49
|
+
// Full Repository Scan
|
|
50
|
+
const { report, findings, exitCode } = await scan({
|
|
51
|
+
repoPath: '/path/to/repo',
|
|
52
|
+
frameworks: ['soc2', 'hipaa'],
|
|
53
|
+
options: {
|
|
54
|
+
severityThreshold: 'high',
|
|
55
|
+
failOn: ['critical', 'high'],
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
console.log(`Found ${findings.length} findings`);
|
|
60
|
+
console.log(`Exit code: ${exitCode}`);
|
|
61
|
+
|
|
62
|
+
// Gate function (for coding agents)
|
|
63
|
+
const result = await gate({
|
|
64
|
+
files: {
|
|
65
|
+
'src/config.ts': 'export const DB_PASSWORD = "hardcoded-secret";',
|
|
66
|
+
'terraform/main.tf': 'resource "aws_s3_bucket" "data" { }',
|
|
67
|
+
},
|
|
68
|
+
frameworks: ['soc2', 'hipaa'],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!result.passed) {
|
|
72
|
+
console.log('Compliance issues found:');
|
|
73
|
+
console.log(result.prompt); // Pre-formatted remediation instructions
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## API Key
|
|
78
|
+
|
|
79
|
+
An API key is required for production use to authenticate with ProdCycle. Set it via environment variable:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
export PC_API_KEY=pc_your_api_key_here
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
API keys are created through the ProdCycle dashboard.
|
|
86
|
+
|
|
87
|
+
## Requirements
|
|
88
|
+
|
|
89
|
+
- Node.js >= 24.0.0
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface ScanOptions {
|
|
2
|
+
severityThreshold?: 'low' | 'medium' | 'high' | 'critical';
|
|
3
|
+
failOn?: ('low' | 'medium' | 'high' | 'critical')[];
|
|
4
|
+
include?: string[];
|
|
5
|
+
exclude?: string[];
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
apiUrl?: string;
|
|
8
|
+
config?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface GateOptions {
|
|
11
|
+
files: Record<string, string>;
|
|
12
|
+
frameworks?: string[];
|
|
13
|
+
severityThreshold?: 'low' | 'medium' | 'high' | 'critical';
|
|
14
|
+
failOn?: ('low' | 'medium' | 'high' | 'critical')[];
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
apiUrl?: string;
|
|
17
|
+
config?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
export declare class ComplianceApiClient {
|
|
20
|
+
private apiUrl;
|
|
21
|
+
private apiKey;
|
|
22
|
+
constructor(apiUrl?: string, apiKey?: string);
|
|
23
|
+
validate(files: Record<string, string>, frameworks: string[], options?: ScanOptions): Promise<any>;
|
|
24
|
+
hook(files: Record<string, string>, frameworks: string[]): Promise<any>;
|
|
25
|
+
private post;
|
|
26
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ComplianceApiClient = void 0;
|
|
4
|
+
class ComplianceApiClient {
|
|
5
|
+
apiUrl;
|
|
6
|
+
apiKey;
|
|
7
|
+
constructor(apiUrl, apiKey) {
|
|
8
|
+
this.apiUrl = apiUrl || process.env.PC_API_URL || 'https://api.prodcycle.com';
|
|
9
|
+
this.apiKey = apiKey || process.env.PC_API_KEY || '';
|
|
10
|
+
if (!this.apiKey && process.env.NODE_ENV !== 'test') {
|
|
11
|
+
console.warn('Warning: PC_API_KEY is not set. API calls will likely fail.');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async validate(files, frameworks, options = {}) {
|
|
15
|
+
return this.post('/v1/compliance/validate', {
|
|
16
|
+
files,
|
|
17
|
+
frameworks,
|
|
18
|
+
options: {
|
|
19
|
+
severity_threshold: options.severityThreshold,
|
|
20
|
+
fail_on: options.failOn,
|
|
21
|
+
...options.config,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async hook(files, frameworks) {
|
|
26
|
+
return this.post('/v1/compliance/hook', {
|
|
27
|
+
files,
|
|
28
|
+
frameworks,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async post(endpoint, data) {
|
|
32
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(url, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify(data),
|
|
41
|
+
});
|
|
42
|
+
const responseData = await response.json();
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
throw new Error(responseData.error?.message || `API request failed with status ${response.status}`);
|
|
45
|
+
}
|
|
46
|
+
return responseData;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new Error(`Failed to connect to ProdCycle API: ${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.ComplianceApiClient = ComplianceApiClient;
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const index_1 = require("./index");
|
|
41
|
+
const program = new commander_1.Command();
|
|
42
|
+
program
|
|
43
|
+
.name('prodcycle')
|
|
44
|
+
.description('Multi-framework policy-as-code compliance scanner for infrastructure and application code.')
|
|
45
|
+
.version('0.1.0')
|
|
46
|
+
.argument('[repo_path]', 'Path to the repository to scan', '.')
|
|
47
|
+
.option('--framework <ids>', 'Comma-separated framework IDs to evaluate', 'soc2')
|
|
48
|
+
.option('--format <format>', 'Output format: json, sarif, table, prompt', 'table')
|
|
49
|
+
.option('--severity-threshold <severity>', 'Minimum severity to include in report', 'low')
|
|
50
|
+
.option('--fail-on <levels>', 'Comma-separated severities that cause non-zero exit', 'critical,high')
|
|
51
|
+
.option('--include <patterns>', 'Comma-separated glob patterns to include')
|
|
52
|
+
.option('--exclude <patterns>', 'Comma-separated glob patterns to exclude')
|
|
53
|
+
.option('--output <file>', 'Write report to file')
|
|
54
|
+
.option('--api-url <url>', 'Compliance API base URL (or PC_API_URL env)')
|
|
55
|
+
.option('--api-key <key>', 'API key for compliance API (or PC_API_KEY env)')
|
|
56
|
+
.option('--hook', 'Run as coding agent post-edit hook (reads stdin)')
|
|
57
|
+
.option('--hook-file <path>', 'File path for hook mode (alternative to stdin)')
|
|
58
|
+
.option('--hook-api', 'Run as API-based hook (calls hosted compliance API)')
|
|
59
|
+
.option('--init', 'Set up compliance hooks for coding agents')
|
|
60
|
+
.option('--agent <agents>', 'Comma-separated agents to configure')
|
|
61
|
+
.action(async (repoPath, opts) => {
|
|
62
|
+
try {
|
|
63
|
+
if (opts.hook || opts.hookApi) {
|
|
64
|
+
// Implement hook logic here
|
|
65
|
+
console.log('Hook mode executed.');
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
if (opts.init) {
|
|
69
|
+
// Implement init logic here
|
|
70
|
+
console.log('Init mode executed.');
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
const frameworks = opts.framework.split(',').map((s) => s.trim());
|
|
74
|
+
const failOn = opts.failOn.split(',').map((s) => s.trim());
|
|
75
|
+
const include = opts.include ? opts.include.split(',') : undefined;
|
|
76
|
+
const exclude = opts.exclude ? opts.exclude.split(',') : undefined;
|
|
77
|
+
console.log(`Scanning ${path.resolve(repoPath)} for ${frameworks.join(', ')}...`);
|
|
78
|
+
const response = await (0, index_1.scan)({
|
|
79
|
+
repoPath,
|
|
80
|
+
frameworks,
|
|
81
|
+
options: {
|
|
82
|
+
severityThreshold: opts.severityThreshold,
|
|
83
|
+
failOn,
|
|
84
|
+
include,
|
|
85
|
+
exclude,
|
|
86
|
+
apiUrl: opts.apiUrl,
|
|
87
|
+
apiKey: opts.apiKey,
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
if (opts.format === 'json') {
|
|
91
|
+
const output = JSON.stringify(response, null, 2);
|
|
92
|
+
if (opts.output) {
|
|
93
|
+
fs.writeFileSync(opts.output, output);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log(output);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.log(`Passed: ${response.passed}`);
|
|
101
|
+
console.log(`Findings: ${response.findings.length}`);
|
|
102
|
+
}
|
|
103
|
+
process.exit(response.exitCode);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error(`\u2717 Error: ${error.message}`);
|
|
107
|
+
process.exit(2);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
program.parse();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatPrompt(report: any): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatSarif(report: any): any;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatSarif = formatSarif;
|
|
4
|
+
function formatSarif(report) {
|
|
5
|
+
return {
|
|
6
|
+
version: '2.1.0',
|
|
7
|
+
runs: [{ tool: { driver: { name: 'ProdCycle Compliance Scanner' } }, results: [] }]
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatTable(report: any): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatTable = formatTable;
|
|
4
|
+
function formatTable(report) {
|
|
5
|
+
// Simplistic table formatter
|
|
6
|
+
if (!report)
|
|
7
|
+
return 'No report data';
|
|
8
|
+
return `Scan Results: ${report.summary?.passed || 0} passed, ${report.summary?.failed || 0} failed.`;
|
|
9
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ScanOptions, GateOptions } from './api-client';
|
|
2
|
+
export * from './api-client';
|
|
3
|
+
export * from './formatters/table';
|
|
4
|
+
export * from './formatters/prompt';
|
|
5
|
+
export * from './formatters/sarif';
|
|
6
|
+
/**
|
|
7
|
+
* Scan a repository by collecting files and sending them to the API
|
|
8
|
+
*/
|
|
9
|
+
export declare function scan(params: {
|
|
10
|
+
repoPath: string;
|
|
11
|
+
frameworks?: string[];
|
|
12
|
+
options?: ScanOptions;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
passed: boolean;
|
|
15
|
+
exitCode: number;
|
|
16
|
+
findings: never[];
|
|
17
|
+
report: null;
|
|
18
|
+
summary?: undefined;
|
|
19
|
+
} | {
|
|
20
|
+
passed: any;
|
|
21
|
+
exitCode: number;
|
|
22
|
+
findings: any;
|
|
23
|
+
report: any;
|
|
24
|
+
summary: any;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Gate code strings directly without writing to disk
|
|
28
|
+
*/
|
|
29
|
+
export declare function gate(options: GateOptions): Promise<{
|
|
30
|
+
passed: any;
|
|
31
|
+
exitCode: number;
|
|
32
|
+
findings: any;
|
|
33
|
+
prompt: any;
|
|
34
|
+
summary: any;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Run local hook
|
|
38
|
+
*/
|
|
39
|
+
export declare function runHook(params: {
|
|
40
|
+
frameworks?: string[];
|
|
41
|
+
filePath?: string;
|
|
42
|
+
}): Promise<number>;
|
|
43
|
+
/**
|
|
44
|
+
* Run API hook
|
|
45
|
+
*/
|
|
46
|
+
export declare function runHookApi(params: {
|
|
47
|
+
apiUrl?: string;
|
|
48
|
+
apiKey?: string;
|
|
49
|
+
frameworks?: string[];
|
|
50
|
+
}): Promise<number>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.scan = scan;
|
|
18
|
+
exports.gate = gate;
|
|
19
|
+
exports.runHook = runHook;
|
|
20
|
+
exports.runHookApi = runHookApi;
|
|
21
|
+
const api_client_1 = require("./api-client");
|
|
22
|
+
const fs_1 = require("./utils/fs");
|
|
23
|
+
__exportStar(require("./api-client"), exports);
|
|
24
|
+
__exportStar(require("./formatters/table"), exports);
|
|
25
|
+
__exportStar(require("./formatters/prompt"), exports);
|
|
26
|
+
__exportStar(require("./formatters/sarif"), exports);
|
|
27
|
+
/**
|
|
28
|
+
* Scan a repository by collecting files and sending them to the API
|
|
29
|
+
*/
|
|
30
|
+
async function scan(params) {
|
|
31
|
+
const { repoPath, frameworks = ['soc2'], options = {} } = params;
|
|
32
|
+
// Collect files
|
|
33
|
+
const files = await (0, fs_1.collectFiles)(repoPath, options.include, options.exclude);
|
|
34
|
+
if (Object.keys(files).length === 0) {
|
|
35
|
+
return {
|
|
36
|
+
passed: true,
|
|
37
|
+
exitCode: 0,
|
|
38
|
+
findings: [],
|
|
39
|
+
report: null
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const client = new api_client_1.ComplianceApiClient(options.apiUrl, options.apiKey);
|
|
43
|
+
const response = await client.validate(files, frameworks, options);
|
|
44
|
+
return {
|
|
45
|
+
passed: response.passed,
|
|
46
|
+
exitCode: response.passed ? 0 : 1,
|
|
47
|
+
findings: response.findings || [],
|
|
48
|
+
report: response.report, // The API should return the full report object if requested, or we synthesize it
|
|
49
|
+
summary: response.summary
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Gate code strings directly without writing to disk
|
|
54
|
+
*/
|
|
55
|
+
async function gate(options) {
|
|
56
|
+
const { files, frameworks = ['soc2'], ...scanOpts } = options;
|
|
57
|
+
const client = new api_client_1.ComplianceApiClient(options.apiUrl, options.apiKey);
|
|
58
|
+
const response = await client.hook(files, frameworks);
|
|
59
|
+
return {
|
|
60
|
+
passed: response.passed,
|
|
61
|
+
exitCode: response.passed ? 0 : 1,
|
|
62
|
+
findings: response.findings || [],
|
|
63
|
+
prompt: response.prompt,
|
|
64
|
+
summary: response.summary
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Run local hook
|
|
69
|
+
*/
|
|
70
|
+
async function runHook(params) {
|
|
71
|
+
// Logic to read stdin or specific file and call gate
|
|
72
|
+
const { frameworks = ['soc2'], filePath } = params;
|
|
73
|
+
// Implementation details...
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Run API hook
|
|
78
|
+
*/
|
|
79
|
+
async function runHookApi(params) {
|
|
80
|
+
// Implementation details...
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function collectFiles(baseDir: string, includePatterns?: string[], excludePatterns?: string[]): Promise<Record<string, string>>;
|
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.collectFiles = collectFiles;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const glob_1 = require("glob");
|
|
40
|
+
const MAX_FILE_SIZE = 256 * 1024; // 256 KB
|
|
41
|
+
const MAX_TOTAL_FILES = 500;
|
|
42
|
+
async function collectFiles(baseDir, includePatterns, excludePatterns) {
|
|
43
|
+
// Simple implementation using glob
|
|
44
|
+
const patterns = includePatterns && includePatterns.length > 0 ? includePatterns : ['**/*'];
|
|
45
|
+
const ignore = ['node_modules/**', '.git/**', '.terraform/**', 'dist/**', 'build/**', '**/__pycache__/**'];
|
|
46
|
+
if (excludePatterns && excludePatterns.length > 0) {
|
|
47
|
+
ignore.push(...excludePatterns);
|
|
48
|
+
}
|
|
49
|
+
const matches = await (0, glob_1.glob)(patterns, {
|
|
50
|
+
cwd: baseDir,
|
|
51
|
+
ignore,
|
|
52
|
+
nodir: true,
|
|
53
|
+
});
|
|
54
|
+
const files = {};
|
|
55
|
+
let count = 0;
|
|
56
|
+
for (const match of matches) {
|
|
57
|
+
if (count >= MAX_TOTAL_FILES) {
|
|
58
|
+
console.warn(`Reached max file limit (${MAX_TOTAL_FILES}). Some files were skipped.`);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
const fullPath = path.join(baseDir, match);
|
|
62
|
+
const stats = fs.statSync(fullPath);
|
|
63
|
+
// Skip large files
|
|
64
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// Basic heuristic to skip binary files
|
|
68
|
+
const buffer = fs.readFileSync(fullPath);
|
|
69
|
+
if (isBinary(buffer)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
files[match] = buffer.toString('utf8');
|
|
73
|
+
count++;
|
|
74
|
+
}
|
|
75
|
+
return files;
|
|
76
|
+
}
|
|
77
|
+
function isBinary(buffer) {
|
|
78
|
+
for (let i = 0; i < Math.min(buffer.length, 1024); i++) {
|
|
79
|
+
if (buffer[i] === 0)
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prodcycle/prodcycle",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Multi-framework policy-as-code compliance scanner for infrastructure and application code.",
|
|
5
|
+
"homepage": "https://docs.prodcycle.com",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/prodcycle/prodcycle-cli.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/prodcycle/prodcycle-cli/issues"
|
|
12
|
+
},
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"bin": {
|
|
19
|
+
"prodcycle": "dist/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"compliance",
|
|
27
|
+
"soc2",
|
|
28
|
+
"hipaa",
|
|
29
|
+
"nist",
|
|
30
|
+
"cli",
|
|
31
|
+
"security"
|
|
32
|
+
],
|
|
33
|
+
"author": "ProdCycle, Inc. <engineering@prodcycle.com>",
|
|
34
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"commander": "^12.0.0",
|
|
37
|
+
"glob": "^10.3.10",
|
|
38
|
+
"ignore": "^5.3.1"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^22.0.0",
|
|
42
|
+
"typescript": "^5.4.0"
|
|
43
|
+
}
|
|
44
|
+
}
|