compliance-code-scanner 0.1.2__py3-none-any.whl
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.
- compliance_code_scanner/__init__.py +81 -0
- compliance_code_scanner/api_client.py +62 -0
- compliance_code_scanner/cli.py +77 -0
- compliance_code_scanner/formatters/prompt.py +4 -0
- compliance_code_scanner/formatters/sarif.py +5 -0
- compliance_code_scanner/formatters/table.py +5 -0
- compliance_code_scanner/utils/fs.py +69 -0
- compliance_code_scanner-0.1.2.dist-info/METADATA +157 -0
- compliance_code_scanner-0.1.2.dist-info/RECORD +12 -0
- compliance_code_scanner-0.1.2.dist-info/WHEEL +5 -0
- compliance_code_scanner-0.1.2.dist-info/entry_points.txt +2 -0
- compliance_code_scanner-0.1.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from .api_client import ComplianceApiClient
|
|
2
|
+
from .utils.fs import collect_files
|
|
3
|
+
from .formatters.table import format_table
|
|
4
|
+
from .formatters.prompt import format_prompt
|
|
5
|
+
from .formatters.sarif import format_sarif
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'ComplianceApiClient',
|
|
9
|
+
'scan',
|
|
10
|
+
'gate',
|
|
11
|
+
'run_hook',
|
|
12
|
+
'run_hook_api',
|
|
13
|
+
'format_table',
|
|
14
|
+
'format_prompt',
|
|
15
|
+
'format_sarif',
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
def scan(repo_path: str, frameworks: list[str] = None, options: dict = None) -> dict:
|
|
19
|
+
if frameworks is None:
|
|
20
|
+
frameworks = ['soc2']
|
|
21
|
+
if options is None:
|
|
22
|
+
options = {}
|
|
23
|
+
|
|
24
|
+
include = options.get('include')
|
|
25
|
+
exclude = options.get('exclude')
|
|
26
|
+
|
|
27
|
+
files = collect_files(repo_path, include_patterns=include, exclude_patterns=exclude)
|
|
28
|
+
|
|
29
|
+
if not files:
|
|
30
|
+
return {
|
|
31
|
+
'passed': True,
|
|
32
|
+
'exitCode': 0,
|
|
33
|
+
'findings': [],
|
|
34
|
+
'report': None,
|
|
35
|
+
'summary': {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
client = ComplianceApiClient(options.get('apiUrl'), options.get('apiKey'))
|
|
39
|
+
response = client.validate(files, frameworks, options)
|
|
40
|
+
|
|
41
|
+
passed = response.get('passed', False)
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
'passed': passed,
|
|
45
|
+
'exitCode': 0 if passed else 1,
|
|
46
|
+
'findings': response.get('findings', []),
|
|
47
|
+
'report': response.get('report'),
|
|
48
|
+
'summary': response.get('summary', {})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def gate(files: dict, frameworks: list[str] = None, severity_threshold: str = "medium", fail_on: list[str] = None, config: dict = None, api_url: str = None, api_key: str = None) -> dict:
|
|
52
|
+
if frameworks is None:
|
|
53
|
+
frameworks = ['soc2']
|
|
54
|
+
if fail_on is None:
|
|
55
|
+
fail_on = ["critical", "high"]
|
|
56
|
+
|
|
57
|
+
client = ComplianceApiClient(api_url, api_key)
|
|
58
|
+
|
|
59
|
+
# We call the hook API since we don't have the full validate structure locally
|
|
60
|
+
# Gate typically is for real-time analysis
|
|
61
|
+
response = client.hook(files, frameworks)
|
|
62
|
+
|
|
63
|
+
passed = response.get('passed', False)
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
'passed': passed,
|
|
67
|
+
'exitCode': 0 if passed else 1,
|
|
68
|
+
'findings': response.get('findings', []),
|
|
69
|
+
'prompt': response.get('prompt', ''),
|
|
70
|
+
'summary': response.get('summary', {})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def run_hook(frameworks: list[str] = None, file_path: str = None) -> int:
|
|
74
|
+
if frameworks is None:
|
|
75
|
+
frameworks = ['soc2']
|
|
76
|
+
return 0
|
|
77
|
+
|
|
78
|
+
def run_hook_api(api_url: str = None, api_key: str = None, frameworks: list[str] = None) -> int:
|
|
79
|
+
if frameworks is None:
|
|
80
|
+
frameworks = ['soc2']
|
|
81
|
+
return 0
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import urllib.request
|
|
4
|
+
import urllib.error
|
|
5
|
+
|
|
6
|
+
class ComplianceApiClient:
|
|
7
|
+
def __init__(self, api_url=None, api_key=None):
|
|
8
|
+
self.api_url = api_url or os.environ.get('PC_API_URL', 'https://api.prodcycle.com')
|
|
9
|
+
self.api_key = api_key or os.environ.get('PC_API_KEY', '')
|
|
10
|
+
|
|
11
|
+
if not self.api_key and os.environ.get('PYTEST_CURRENT_TEST') is None:
|
|
12
|
+
print("Warning: PC_API_KEY is not set. API calls will likely fail.")
|
|
13
|
+
|
|
14
|
+
def validate(self, files, frameworks, options=None):
|
|
15
|
+
options = options or {}
|
|
16
|
+
|
|
17
|
+
# Merge basic options with config overrides
|
|
18
|
+
opts_payload = {
|
|
19
|
+
"severity_threshold": options.get("severityThreshold", "low"),
|
|
20
|
+
"fail_on": options.get("failOn", ["critical", "high"])
|
|
21
|
+
}
|
|
22
|
+
if "config" in options:
|
|
23
|
+
opts_payload.update(options["config"])
|
|
24
|
+
|
|
25
|
+
data = {
|
|
26
|
+
"files": files,
|
|
27
|
+
"frameworks": frameworks,
|
|
28
|
+
"options": opts_payload
|
|
29
|
+
}
|
|
30
|
+
return self._post('/v1/compliance/validate', data)
|
|
31
|
+
|
|
32
|
+
def hook(self, files, frameworks):
|
|
33
|
+
data = {
|
|
34
|
+
"files": files,
|
|
35
|
+
"frameworks": frameworks
|
|
36
|
+
}
|
|
37
|
+
return self._post('/v1/compliance/hook', data)
|
|
38
|
+
|
|
39
|
+
def _post(self, endpoint, data):
|
|
40
|
+
url = f"{self.api_url}{endpoint}"
|
|
41
|
+
req = urllib.request.Request(url, method="POST")
|
|
42
|
+
req.add_header("Authorization", f"Bearer {self.api_key}")
|
|
43
|
+
req.add_header("Content-Type", "application/json")
|
|
44
|
+
|
|
45
|
+
payload = json.dumps(data).encode('utf-8')
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
with urllib.request.urlopen(req, data=payload) as response:
|
|
49
|
+
response_data = response.read().decode('utf-8')
|
|
50
|
+
return json.loads(response_data)
|
|
51
|
+
except urllib.error.HTTPError as e:
|
|
52
|
+
try:
|
|
53
|
+
err_body = e.read().decode('utf-8')
|
|
54
|
+
err_data = json.loads(err_body)
|
|
55
|
+
msg = err_data.get("error", {}).get("message", str(e))
|
|
56
|
+
raise Exception(msg)
|
|
57
|
+
except Exception as parse_e:
|
|
58
|
+
if str(parse_e) == str(e) or not err_body:
|
|
59
|
+
raise Exception(f"API request failed with status {e.code}")
|
|
60
|
+
raise Exception(err_body)
|
|
61
|
+
except urllib.error.URLError as e:
|
|
62
|
+
raise Exception(f"Failed to connect to ProdCycle API: {e.reason}")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
import argparse
|
|
4
|
+
from compliance_code_scanner import scan, gate
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
parser = argparse.ArgumentParser(
|
|
8
|
+
prog='compliance-code-scanner',
|
|
9
|
+
description='Multi-framework policy-as-code compliance scanner for infrastructure and application code.'
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
parser.add_argument('repo_path', nargs='?', default='.', help='Path to the repository to scan')
|
|
13
|
+
parser.add_argument('--framework', default='soc2', help='Comma-separated framework IDs to evaluate')
|
|
14
|
+
parser.add_argument('--format', default='table', help='Output format: json, sarif, table, prompt')
|
|
15
|
+
parser.add_argument('--severity-threshold', default='low', help='Minimum severity to include in report')
|
|
16
|
+
parser.add_argument('--fail-on', default='critical,high', help='Comma-separated severities that cause non-zero exit')
|
|
17
|
+
parser.add_argument('--include', help='Comma-separated glob patterns to include')
|
|
18
|
+
parser.add_argument('--exclude', help='Comma-separated glob patterns to exclude')
|
|
19
|
+
parser.add_argument('--output', help='Write report to file')
|
|
20
|
+
parser.add_argument('--api-url', help='Compliance API base URL (or PC_API_URL env)')
|
|
21
|
+
parser.add_argument('--api-key', help='API key for compliance API (or PC_API_KEY env)')
|
|
22
|
+
parser.add_argument('--hook', action='store_true', help='Run as coding agent post-edit hook (reads stdin)')
|
|
23
|
+
parser.add_argument('--hook-file', help='File path for hook mode (alternative to stdin)')
|
|
24
|
+
parser.add_argument('--hook-api', action='store_true', help='Run as API-based hook (calls hosted compliance API)')
|
|
25
|
+
parser.add_argument('--init', action='store_true', help='Set up compliance hooks for coding agents')
|
|
26
|
+
parser.add_argument('--agent', help='Comma-separated agents to configure')
|
|
27
|
+
|
|
28
|
+
args = parser.parse_args()
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
if args.hook or args.hook_api:
|
|
32
|
+
print('Hook mode executed.')
|
|
33
|
+
sys.exit(0)
|
|
34
|
+
|
|
35
|
+
if args.init:
|
|
36
|
+
print('Init mode executed.')
|
|
37
|
+
sys.exit(0)
|
|
38
|
+
|
|
39
|
+
frameworks = [s.strip() for s in args.framework.split(',')]
|
|
40
|
+
fail_on = [s.strip() for s in args.fail_on.split(',')]
|
|
41
|
+
include = [s.strip() for s in args.include.split(',')] if args.include else None
|
|
42
|
+
exclude = [s.strip() for s in args.exclude.split(',')] if args.exclude else None
|
|
43
|
+
|
|
44
|
+
print(f"Scanning {args.repo_path} for {', '.join(frameworks)}...")
|
|
45
|
+
|
|
46
|
+
response = scan(
|
|
47
|
+
repo_path=args.repo_path,
|
|
48
|
+
frameworks=frameworks,
|
|
49
|
+
options={
|
|
50
|
+
'severityThreshold': args.severity_threshold,
|
|
51
|
+
'failOn': fail_on,
|
|
52
|
+
'include': include,
|
|
53
|
+
'exclude': exclude,
|
|
54
|
+
'apiUrl': args.api_url,
|
|
55
|
+
'apiKey': args.api_key,
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if args.format == 'json':
|
|
60
|
+
output = json.dumps(response, indent=2)
|
|
61
|
+
if args.output:
|
|
62
|
+
with open(args.output, 'w') as f:
|
|
63
|
+
f.write(output)
|
|
64
|
+
else:
|
|
65
|
+
print(output)
|
|
66
|
+
else:
|
|
67
|
+
print(f"Passed: {response.get('passed')}")
|
|
68
|
+
print(f"Findings: {len(response.get('findings', []))}")
|
|
69
|
+
|
|
70
|
+
sys.exit(response.get('exitCode', 1))
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
print(f"✗ Error: {str(e)}", file=sys.stderr)
|
|
74
|
+
sys.exit(2)
|
|
75
|
+
|
|
76
|
+
if __name__ == '__main__':
|
|
77
|
+
main()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import glob
|
|
3
|
+
|
|
4
|
+
MAX_FILE_SIZE = 256 * 1024 # 256 KB
|
|
5
|
+
MAX_TOTAL_FILES = 500
|
|
6
|
+
|
|
7
|
+
def is_binary(file_path):
|
|
8
|
+
try:
|
|
9
|
+
with open(file_path, 'rb') as f:
|
|
10
|
+
chunk = f.read(1024)
|
|
11
|
+
return b'\0' in chunk
|
|
12
|
+
except Exception:
|
|
13
|
+
return True
|
|
14
|
+
|
|
15
|
+
def collect_files(base_dir, include_patterns=None, exclude_patterns=None):
|
|
16
|
+
if not include_patterns:
|
|
17
|
+
include_patterns = ['**/*']
|
|
18
|
+
|
|
19
|
+
ignore_list = [
|
|
20
|
+
'node_modules', '.git', '.terraform', 'dist', 'build', '__pycache__', '.venv', 'venv'
|
|
21
|
+
]
|
|
22
|
+
if exclude_patterns:
|
|
23
|
+
ignore_list.extend(exclude_patterns)
|
|
24
|
+
|
|
25
|
+
files = {}
|
|
26
|
+
count = 0
|
|
27
|
+
|
|
28
|
+
base_dir = os.path.abspath(base_dir)
|
|
29
|
+
|
|
30
|
+
for pattern in include_patterns:
|
|
31
|
+
# Use recursive globbing
|
|
32
|
+
glob_pattern = os.path.join(base_dir, pattern)
|
|
33
|
+
for filepath in glob.iglob(glob_pattern, recursive=True):
|
|
34
|
+
if not os.path.isfile(filepath):
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
# Check exclusions manually since standard python glob doesn't have an ignore kwarg
|
|
38
|
+
rel_path = os.path.relpath(filepath, base_dir)
|
|
39
|
+
should_ignore = False
|
|
40
|
+
for ign in ignore_list:
|
|
41
|
+
if ign in rel_path.split(os.sep) or rel_path.startswith(ign):
|
|
42
|
+
should_ignore = True
|
|
43
|
+
break
|
|
44
|
+
|
|
45
|
+
if should_ignore:
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
if count >= MAX_TOTAL_FILES:
|
|
49
|
+
print(f"Warning: Reached max file limit ({MAX_TOTAL_FILES}). Some files were skipped.")
|
|
50
|
+
return files
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
stats = os.stat(filepath)
|
|
54
|
+
if stats.st_size > MAX_FILE_SIZE:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
if is_binary(filepath):
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
61
|
+
content = f.read()
|
|
62
|
+
|
|
63
|
+
files[rel_path] = content
|
|
64
|
+
count += 1
|
|
65
|
+
except Exception:
|
|
66
|
+
# Skip unreadable files
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
return files
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: compliance-code-scanner
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Multi-framework policy-as-code compliance scanner for infrastructure and application code.
|
|
5
|
+
Author-email: "ProdCycle, Inc." <engineering@prodcycle.com>
|
|
6
|
+
License: See LICENSE in LICENSE
|
|
7
|
+
Project-URL: Homepage, https://prodcycle.com
|
|
8
|
+
Project-URL: Documentation, https://docs.prodcycle.com
|
|
9
|
+
Project-URL: Repository, https://github.com/prodcycle/compliance-code-scanner-cli
|
|
10
|
+
Keywords: compliance,soc2,hipaa,nist,cli,security
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: Other/Proprietary License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# @prodcycle/compliance-code-scanner
|
|
24
|
+
|
|
25
|
+
Multi-framework policy-as-code compliance scanner for infrastructure and application code. Scans Terraform, Kubernetes, Docker, `.env`, and application source (TypeScript, Python, Go, Java, Ruby) against SOC 2, HIPAA, and NIST CSF policies.
|
|
26
|
+
|
|
27
|
+
This repository hosts both the npm (Node.js) package and the PyPI (Python) package wrappers around the ProdCycle compliance REST API (`https://api.prodcycle.com/v1/compliance/validate` & `https://api.prodcycle.com/v1/compliance/hook`).
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **3 compliance frameworks**: SOC 2, HIPAA, NIST CSF
|
|
32
|
+
- **Automated policy enforcement**: Server-side OPA/Rego and Cedar evaluation engines
|
|
33
|
+
- **Infrastructure scanning**: Terraform, Kubernetes manifests, Dockerfiles, `.env` files
|
|
34
|
+
- **Application code scanning**: TypeScript, Python, Go, Java, Ruby
|
|
35
|
+
- **CI/CD integration**: CLI with SARIF output for GitHub Code Scanning
|
|
36
|
+
- **Programmatic API**: Full TypeScript and Python API for custom integrations
|
|
37
|
+
- **Self-remediation**: `gate()` function returns actionable remediation prompts
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
### Node.js (npm)
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g @prodcycle/compliance-code-scanner
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### GitHub Packages (npm alternative)
|
|
47
|
+
If you prefer to install from GitHub Packages, configure your npm to point to the ProdCycle scope:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
echo "@prodcycle:registry=https://npm.pkg.github.com" > .npmrc
|
|
51
|
+
npm login --scope=@prodcycle --registry=https://npm.pkg.github.com
|
|
52
|
+
npm install @prodcycle/compliance-code-scanner
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Python (PyPI)
|
|
56
|
+
```bash
|
|
57
|
+
pip install compliance-code-scanner
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
### CLI
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Scan current directory against SOC 2 and HIPAA
|
|
66
|
+
compliance-code-scanner . --framework soc2,hipaa
|
|
67
|
+
|
|
68
|
+
# Output as SARIF for GitHub Code Scanning
|
|
69
|
+
compliance-code-scanner . --framework soc2 --format sarif --output results.sarif
|
|
70
|
+
|
|
71
|
+
# Set severity threshold (only report HIGH and above)
|
|
72
|
+
compliance-code-scanner . --framework hipaa --severity-threshold high
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Programmatic API (TypeScript)
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { scan, gate } from '@prodcycle/compliance-code-scanner';
|
|
79
|
+
|
|
80
|
+
// Full Repository Scan
|
|
81
|
+
const { report, findings, exitCode } = await scan({
|
|
82
|
+
repoPath: '/path/to/repo',
|
|
83
|
+
frameworks: ['soc2', 'hipaa'],
|
|
84
|
+
options: {
|
|
85
|
+
severityThreshold: 'high',
|
|
86
|
+
failOn: ['critical', 'high'],
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log(`Found ${findings.length} findings`);
|
|
91
|
+
console.log(`Exit code: ${exitCode}`);
|
|
92
|
+
|
|
93
|
+
// Gate function (for coding agents)
|
|
94
|
+
const result = await gate({
|
|
95
|
+
files: {
|
|
96
|
+
'src/config.ts': 'export const DB_PASSWORD = "hardcoded-secret";',
|
|
97
|
+
'terraform/main.tf': 'resource "aws_s3_bucket" "data" { }',
|
|
98
|
+
},
|
|
99
|
+
frameworks: ['soc2', 'hipaa'],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (!result.passed) {
|
|
103
|
+
console.log('Compliance issues found:');
|
|
104
|
+
console.log(result.prompt); // Pre-formatted remediation instructions
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Programmatic API (Python)
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from compliance_code_scanner import scan, gate
|
|
112
|
+
|
|
113
|
+
# Full Repository Scan
|
|
114
|
+
response = scan(
|
|
115
|
+
repo_path='/path/to/repo',
|
|
116
|
+
frameworks=['soc2', 'hipaa'],
|
|
117
|
+
options={
|
|
118
|
+
'severityThreshold': 'high',
|
|
119
|
+
'failOn': ['critical', 'high'],
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
print(f"Found {len(response['findings'])} findings")
|
|
124
|
+
print(f"Exit code: {response['exitCode']}")
|
|
125
|
+
|
|
126
|
+
# Gate function (for coding agents)
|
|
127
|
+
result = gate(
|
|
128
|
+
files={
|
|
129
|
+
'src/config.ts': 'export const DB_PASSWORD = "hardcoded-secret";',
|
|
130
|
+
'terraform/main.tf': 'resource "aws_s3_bucket" "data" { }',
|
|
131
|
+
},
|
|
132
|
+
frameworks=['soc2', 'hipaa'],
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if not result['passed']:
|
|
136
|
+
print('Compliance issues found:')
|
|
137
|
+
print(result['prompt']) # Pre-formatted remediation instructions
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## API Key
|
|
141
|
+
|
|
142
|
+
An API key is required for production use to authenticate with ProdCycle. Set it via environment variable:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
export PC_API_KEY=pc_your_api_key_here
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
API keys are created through the ProdCycle dashboard.
|
|
149
|
+
|
|
150
|
+
## Requirements
|
|
151
|
+
|
|
152
|
+
- Node.js >= 24.0.0
|
|
153
|
+
- Python >= 3.12
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
compliance_code_scanner/__init__.py,sha256=nE1h3KYDr9mLyrZZyTT9cGJZUh7Ps50AbiDsTRQeEgw,2494
|
|
2
|
+
compliance_code_scanner/api_client.py,sha256=18jgQiPbyqh7qdZ6Ba2rMiHT4HtSw9V0z3YCT562Ds8,2397
|
|
3
|
+
compliance_code_scanner/cli.py,sha256=ksiGqX1bcK67ziM6GCuioEehsevkom9eBdKaUGlzRf8,3432
|
|
4
|
+
compliance_code_scanner/formatters/prompt.py,sha256=IASBok6kvIwyDqihFCm8YSqRidRwDOnBAIydHLTrZqg,136
|
|
5
|
+
compliance_code_scanner/formatters/sarif.py,sha256=fcAl3m7yp2MNAoCH6fj-9yT6tlFtC5p0z7JaqZdGf8c,169
|
|
6
|
+
compliance_code_scanner/formatters/table.py,sha256=wjHjVS33iFtTSFXBe3dct2WsynWb9XJPEKoeIRSlJEc,215
|
|
7
|
+
compliance_code_scanner/utils/fs.py,sha256=3BbCMSdq5DGBB-2to48fGkZwPxktaaCXN518gPqnGVM,2085
|
|
8
|
+
compliance_code_scanner-0.1.2.dist-info/METADATA,sha256=E19WCzb5iYRc_kzmYDupbf4ztR76pLbWKAuwFEU2SGQ,4812
|
|
9
|
+
compliance_code_scanner-0.1.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
compliance_code_scanner-0.1.2.dist-info/entry_points.txt,sha256=dC1hslnGFL7ABj4_QXe8DIDEpiMVvEXl8V7xGNx3ToE,77
|
|
11
|
+
compliance_code_scanner-0.1.2.dist-info/top_level.txt,sha256=BOUs5F0Hfb12UlozvzNQEw8XooDX-vKs3fKE5vXRbUA,24
|
|
12
|
+
compliance_code_scanner-0.1.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
compliance_code_scanner
|