credactor 2.3.2__tar.gz → 2.3.3__tar.gz
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.
- credactor-2.3.3/PKG-INFO +152 -0
- credactor-2.3.3/README.md +125 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor/__init__.py +1 -1
- {credactor-2.3.2 → credactor-2.3.3}/credactor/config.py +32 -7
- {credactor-2.3.2 → credactor-2.3.3}/credactor/report.py +17 -7
- {credactor-2.3.2 → credactor-2.3.3}/credactor/scanner.py +9 -2
- credactor-2.3.3/credactor.egg-info/PKG-INFO +152 -0
- {credactor-2.3.2 → credactor-2.3.3}/pyproject.toml +1 -1
- credactor-2.3.3/tests/test_security.py +323 -0
- credactor-2.3.2/PKG-INFO +0 -81
- credactor-2.3.2/README.md +0 -54
- credactor-2.3.2/credactor.egg-info/PKG-INFO +0 -81
- credactor-2.3.2/tests/test_security.py +0 -128
- {credactor-2.3.2 → credactor-2.3.3}/LICENSE +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor/__main__.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor/cli.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor/gitignore.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor/patterns.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor/redactor.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor/suppressions.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor/utils.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor/walker.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor.egg-info/SOURCES.txt +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor.egg-info/dependency_links.txt +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor.egg-info/entry_points.txt +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor.egg-info/requires.txt +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/credactor.egg-info/top_level.txt +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/setup.cfg +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/tests/test_cli.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/tests/test_config.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/tests/test_gitignore.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/tests/test_patterns.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/tests/test_redactor.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/tests/test_report.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/tests/test_safe_values.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/tests/test_scanner.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/tests/test_suppressions.py +0 -0
- {credactor-2.3.2 → credactor-2.3.3}/tests/test_walker.py +0 -0
credactor-2.3.3/PKG-INFO
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: credactor
|
|
3
|
+
Version: 2.3.3
|
|
4
|
+
Summary: Scan and redact hardcoded credentials in source code
|
|
5
|
+
Author: Rx
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Repository, https://github.com/rxb06/Credactor
|
|
8
|
+
Keywords: security,credentials,secrets,scanner,redactor
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Security
|
|
17
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: encoding
|
|
22
|
+
Requires-Dist: charset-normalizer>=3.0; extra == "encoding"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
25
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
[](https://pypi.org/project/credactor/)
|
|
29
|
+
[](https://github.com/rxb06/Credactor/actions/workflows/ci.yml)
|
|
30
|
+
[](LICENSE)
|
|
31
|
+
|
|
32
|
+
# Credactor
|
|
33
|
+
|
|
34
|
+
> **TL;DR:** Detect and redact hardcoded credentials before they hit version control. Regex + entropy + context-aware analysis, SARIF output, pre-commit hooks, parallel scanning, automated redaction.
|
|
35
|
+
|
|
36
|
+
Credactor scans source code for hardcoded secrets — API keys, tokens, passwords, private keys, connection strings — and replaces them with language-aware environment variable references before they reach version control. It runs as a CLI tool, a pre-commit hook, or in CI pipelines. SARIF output plugs straight into GitHub Code Scanning.
|
|
37
|
+
|
|
38
|
+
<img width="1280" height="640" alt="credactor" src="https://github.com/user-attachments/assets/f1f94a9c-feea-4b8b-9ea4-81f25f07c4df" />
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Why Credactor?
|
|
43
|
+
|
|
44
|
+
Most secret scanners stop at detection. Credactor redacts in place and replaces credentials with the right env var syntax for each language — `os.environ` in Python, `process.env` in JS, `System.getenv` in Java, and so on. It assigns severity levels so you can triage critical findings first instead of wading through noise.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install credactor
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
From source:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git clone https://github.com/rxb06/Credactor.git
|
|
56
|
+
cd Credactor
|
|
57
|
+
pip install -e .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
After either method, `credactor` works from any directory.
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
> Always run `--dry-run` first and review findings before redacting. False positives are possible — use `# credactor:ignore` or `.credactorignore` to suppress them.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Scan current directory (dry run)
|
|
68
|
+
credactor --dry-run .
|
|
69
|
+
|
|
70
|
+
# Scan and interactively redact
|
|
71
|
+
credactor .
|
|
72
|
+
|
|
73
|
+
# Redact everything without prompting
|
|
74
|
+
credactor --fix-all .
|
|
75
|
+
|
|
76
|
+
# CI mode — exit 1 on findings
|
|
77
|
+
credactor --ci .
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Pre-commit Hook
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
# .pre-commit-config.yaml
|
|
84
|
+
repos:
|
|
85
|
+
- repo: https://github.com/rxb06/Credactor
|
|
86
|
+
rev: v2.3.3
|
|
87
|
+
hooks:
|
|
88
|
+
- id: credactor
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or run as a module:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
python -m credactor .
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Detection
|
|
98
|
+
|
|
99
|
+
| Category | Examples | Severity |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| Cloud provider keys | AWS (`AKIA...`), GCP (`AIza...`), Stripe (`sk_live_...`), Slack (`xoxb-...`) | Critical |
|
|
102
|
+
| Platform tokens | GitHub (`ghp_`, `github_pat_`), GitLab (`glpat-`), npm (`npm_`), PyPI (`pypi-`) | Critical |
|
|
103
|
+
| Private keys | PEM blocks (`-----BEGIN RSA PRIVATE KEY-----`) | Critical |
|
|
104
|
+
| JWT tokens | `eyJ...` three-segment tokens | High |
|
|
105
|
+
| Connection strings | `postgresql://user:pass@host`, `mongodb+srv://...`, `redis://...` | High |
|
|
106
|
+
| Variable assignments | `password = "..."`, `api_key = "..."`, `db_password = "..."` | High/Medium |
|
|
107
|
+
| XML attributes | `<add key="Password" value="..." />` | High |
|
|
108
|
+
| High-entropy strings | Hex (32-64 chars), Base64 (60+ chars) | Medium/Low |
|
|
109
|
+
|
|
110
|
+
## Features
|
|
111
|
+
|
|
112
|
+
- Entropy-based detection with per-pattern thresholds to cut false positives
|
|
113
|
+
- Interactive or batch redaction — review one-by-one, or `--fix-all`
|
|
114
|
+
- Language-aware replacements (`os.environ`, `process.env`, `System.getenv`, etc.)
|
|
115
|
+
- Git history scanning via `--scan-history`
|
|
116
|
+
- `.bak` backups before any file modification
|
|
117
|
+
- Inline `# credactor:ignore` suppression and `.credactorignore` allowlists
|
|
118
|
+
- Per-repo config via `.credactor.toml`
|
|
119
|
+
- Parallel scanning (8 workers) for large repos
|
|
120
|
+
- SARIF 2.1.0 output with column-level annotations for GitHub Code Scanning
|
|
121
|
+
- `--fail-on-error` to catch files that couldn't be scanned
|
|
122
|
+
|
|
123
|
+
## Scanned File Types
|
|
124
|
+
|
|
125
|
+
`.py` `.js` `.ts` `.jsx` `.tsx` `.sh` `.bash` `.env` `.env.*` `.cfg` `.ini` `.toml` `.yaml` `.yml` `.rb` `.go` `.java` `.php` `.cs` `.kt` `.tf` `.hcl` `.conf` `.properties` `.xml`
|
|
126
|
+
|
|
127
|
+
JSON files are excluded by default (high false-positive rate from API responses). Use `--scan-json` to include them.
|
|
128
|
+
|
|
129
|
+
## Exit Codes
|
|
130
|
+
|
|
131
|
+
| Code | Meaning |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `0` | No findings, or all resolved |
|
|
134
|
+
| `1` | Unresolved findings |
|
|
135
|
+
| `2` | Error, or files skipped with `--fail-on-error` |
|
|
136
|
+
|
|
137
|
+
## Docs
|
|
138
|
+
|
|
139
|
+
| Document | Description |
|
|
140
|
+
|----------|-------------|
|
|
141
|
+
| [Setup Guide](docs/setup.md) | Installation, configuration, CI/CD integration |
|
|
142
|
+
| [User Guide](docs/user-guide.md) | CLI reference, replacement modes, backup safety |
|
|
143
|
+
| [Examples](docs/examples.md) | Common workflows with output |
|
|
144
|
+
| [Integration](docs/integration.md) | Pre-commit hooks, CI pipelines |
|
|
145
|
+
| [Security](docs/security.md) | Threat model, hardening measures, known limitations |
|
|
146
|
+
| [Changelog](CHANGELOG.md) | Version history |
|
|
147
|
+
| [Contributing](CONTRIBUTING.md) | Development setup, code style, PR process |
|
|
148
|
+
| [Disclaimer](docs/DISCLAIMER.md) | Limitations, safe usage, warranty |
|
|
149
|
+
|
|
150
|
+
## Licence
|
|
151
|
+
|
|
152
|
+
Apache 2.0. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
[](https://pypi.org/project/credactor/)
|
|
2
|
+
[](https://github.com/rxb06/Credactor/actions/workflows/ci.yml)
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
|
|
5
|
+
# Credactor
|
|
6
|
+
|
|
7
|
+
> **TL;DR:** Detect and redact hardcoded credentials before they hit version control. Regex + entropy + context-aware analysis, SARIF output, pre-commit hooks, parallel scanning, automated redaction.
|
|
8
|
+
|
|
9
|
+
Credactor scans source code for hardcoded secrets — API keys, tokens, passwords, private keys, connection strings — and replaces them with language-aware environment variable references before they reach version control. It runs as a CLI tool, a pre-commit hook, or in CI pipelines. SARIF output plugs straight into GitHub Code Scanning.
|
|
10
|
+
|
|
11
|
+
<img width="1280" height="640" alt="credactor" src="https://github.com/user-attachments/assets/f1f94a9c-feea-4b8b-9ea4-81f25f07c4df" />
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Why Credactor?
|
|
16
|
+
|
|
17
|
+
Most secret scanners stop at detection. Credactor redacts in place and replaces credentials with the right env var syntax for each language — `os.environ` in Python, `process.env` in JS, `System.getenv` in Java, and so on. It assigns severity levels so you can triage critical findings first instead of wading through noise.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install credactor
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
From source:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git clone https://github.com/rxb06/Credactor.git
|
|
29
|
+
cd Credactor
|
|
30
|
+
pip install -e .
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
After either method, `credactor` works from any directory.
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
> Always run `--dry-run` first and review findings before redacting. False positives are possible — use `# credactor:ignore` or `.credactorignore` to suppress them.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Scan current directory (dry run)
|
|
41
|
+
credactor --dry-run .
|
|
42
|
+
|
|
43
|
+
# Scan and interactively redact
|
|
44
|
+
credactor .
|
|
45
|
+
|
|
46
|
+
# Redact everything without prompting
|
|
47
|
+
credactor --fix-all .
|
|
48
|
+
|
|
49
|
+
# CI mode — exit 1 on findings
|
|
50
|
+
credactor --ci .
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Pre-commit Hook
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
# .pre-commit-config.yaml
|
|
57
|
+
repos:
|
|
58
|
+
- repo: https://github.com/rxb06/Credactor
|
|
59
|
+
rev: v2.3.3
|
|
60
|
+
hooks:
|
|
61
|
+
- id: credactor
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Or run as a module:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
python -m credactor .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Detection
|
|
71
|
+
|
|
72
|
+
| Category | Examples | Severity |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| Cloud provider keys | AWS (`AKIA...`), GCP (`AIza...`), Stripe (`sk_live_...`), Slack (`xoxb-...`) | Critical |
|
|
75
|
+
| Platform tokens | GitHub (`ghp_`, `github_pat_`), GitLab (`glpat-`), npm (`npm_`), PyPI (`pypi-`) | Critical |
|
|
76
|
+
| Private keys | PEM blocks (`-----BEGIN RSA PRIVATE KEY-----`) | Critical |
|
|
77
|
+
| JWT tokens | `eyJ...` three-segment tokens | High |
|
|
78
|
+
| Connection strings | `postgresql://user:pass@host`, `mongodb+srv://...`, `redis://...` | High |
|
|
79
|
+
| Variable assignments | `password = "..."`, `api_key = "..."`, `db_password = "..."` | High/Medium |
|
|
80
|
+
| XML attributes | `<add key="Password" value="..." />` | High |
|
|
81
|
+
| High-entropy strings | Hex (32-64 chars), Base64 (60+ chars) | Medium/Low |
|
|
82
|
+
|
|
83
|
+
## Features
|
|
84
|
+
|
|
85
|
+
- Entropy-based detection with per-pattern thresholds to cut false positives
|
|
86
|
+
- Interactive or batch redaction — review one-by-one, or `--fix-all`
|
|
87
|
+
- Language-aware replacements (`os.environ`, `process.env`, `System.getenv`, etc.)
|
|
88
|
+
- Git history scanning via `--scan-history`
|
|
89
|
+
- `.bak` backups before any file modification
|
|
90
|
+
- Inline `# credactor:ignore` suppression and `.credactorignore` allowlists
|
|
91
|
+
- Per-repo config via `.credactor.toml`
|
|
92
|
+
- Parallel scanning (8 workers) for large repos
|
|
93
|
+
- SARIF 2.1.0 output with column-level annotations for GitHub Code Scanning
|
|
94
|
+
- `--fail-on-error` to catch files that couldn't be scanned
|
|
95
|
+
|
|
96
|
+
## Scanned File Types
|
|
97
|
+
|
|
98
|
+
`.py` `.js` `.ts` `.jsx` `.tsx` `.sh` `.bash` `.env` `.env.*` `.cfg` `.ini` `.toml` `.yaml` `.yml` `.rb` `.go` `.java` `.php` `.cs` `.kt` `.tf` `.hcl` `.conf` `.properties` `.xml`
|
|
99
|
+
|
|
100
|
+
JSON files are excluded by default (high false-positive rate from API responses). Use `--scan-json` to include them.
|
|
101
|
+
|
|
102
|
+
## Exit Codes
|
|
103
|
+
|
|
104
|
+
| Code | Meaning |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `0` | No findings, or all resolved |
|
|
107
|
+
| `1` | Unresolved findings |
|
|
108
|
+
| `2` | Error, or files skipped with `--fail-on-error` |
|
|
109
|
+
|
|
110
|
+
## Docs
|
|
111
|
+
|
|
112
|
+
| Document | Description |
|
|
113
|
+
|----------|-------------|
|
|
114
|
+
| [Setup Guide](docs/setup.md) | Installation, configuration, CI/CD integration |
|
|
115
|
+
| [User Guide](docs/user-guide.md) | CLI reference, replacement modes, backup safety |
|
|
116
|
+
| [Examples](docs/examples.md) | Common workflows with output |
|
|
117
|
+
| [Integration](docs/integration.md) | Pre-commit hooks, CI pipelines |
|
|
118
|
+
| [Security](docs/security.md) | Threat model, hardening measures, known limitations |
|
|
119
|
+
| [Changelog](CHANGELOG.md) | Version history |
|
|
120
|
+
| [Contributing](CONTRIBUTING.md) | Development setup, code style, PR process |
|
|
121
|
+
| [Disclaimer](docs/DISCLAIMER.md) | Limitations, safe usage, warranty |
|
|
122
|
+
|
|
123
|
+
## Licence
|
|
124
|
+
|
|
125
|
+
Apache 2.0. See [LICENSE](LICENSE).
|
|
@@ -94,10 +94,22 @@ def load_config_file(
|
|
|
94
94
|
# Normpath for cross-platform separator normalisation, then
|
|
95
95
|
# append os.sep AFTER normpath to prevent prefix collision.
|
|
96
96
|
_cand = os.path.normpath(str(candidate.resolve()))
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
_scan = os.path.normpath(str(Path(root).resolve()))
|
|
98
|
+
|
|
99
|
+
if project_root:
|
|
100
|
+
_root = os.path.normpath(str(project_root))
|
|
101
|
+
outside = not (
|
|
102
|
+
_cand == _root or _cand.startswith(_root + os.sep)
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
# SEC-39: No project root found (no .git). Fall back to
|
|
106
|
+
# checking against the scan root — warn if config is in
|
|
107
|
+
# a parent directory, since we cannot verify trust.
|
|
108
|
+
outside = not (
|
|
109
|
+
_cand == _scan or _cand.startswith(_scan + os.sep)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if outside:
|
|
101
113
|
if ci_mode:
|
|
102
114
|
# SEC-29: Hard block in CI — never trust external config
|
|
103
115
|
print(
|
|
@@ -106,9 +118,10 @@ def load_config_file(
|
|
|
106
118
|
file=sys.stderr,
|
|
107
119
|
)
|
|
108
120
|
return {}
|
|
121
|
+
ref = project_root or root
|
|
109
122
|
print(
|
|
110
123
|
f'[WARN] Config loaded from outside project root: '
|
|
111
|
-
f'{candidate} (project root: {
|
|
124
|
+
f'{candidate} (project root: {ref})',
|
|
112
125
|
file=sys.stderr,
|
|
113
126
|
)
|
|
114
127
|
return _parse_toml(candidate)
|
|
@@ -173,7 +186,13 @@ def _basic_toml_parse(path: Path) -> dict:
|
|
|
173
186
|
def apply_config_file(config: Config, file_data: dict) -> None:
|
|
174
187
|
"""Merge values from a parsed config file into the Config object."""
|
|
175
188
|
if 'entropy_threshold' in file_data:
|
|
176
|
-
|
|
189
|
+
# SEC-38: Guard against type confusion (e.g. array where scalar expected).
|
|
190
|
+
try:
|
|
191
|
+
val = float(file_data['entropy_threshold'])
|
|
192
|
+
except (ValueError, TypeError):
|
|
193
|
+
print('[WARN] entropy_threshold has invalid type, using default 3.5',
|
|
194
|
+
file=sys.stderr)
|
|
195
|
+
val = 3.5
|
|
177
196
|
# SEC-12: Bound entropy threshold to valid Shannon entropy range
|
|
178
197
|
if not 0.0 <= val <= 6.0:
|
|
179
198
|
print(f'[WARN] entropy_threshold={val} out of valid range (0.0-6.0), '
|
|
@@ -181,7 +200,13 @@ def apply_config_file(config: Config, file_data: dict) -> None:
|
|
|
181
200
|
val = 3.5
|
|
182
201
|
config.entropy_threshold = val
|
|
183
202
|
if 'min_value_length' in file_data:
|
|
184
|
-
|
|
203
|
+
# SEC-38: Guard against type confusion.
|
|
204
|
+
try:
|
|
205
|
+
val_i = int(file_data['min_value_length'])
|
|
206
|
+
except (ValueError, TypeError):
|
|
207
|
+
print('[WARN] min_value_length has invalid type, using default 8',
|
|
208
|
+
file=sys.stderr)
|
|
209
|
+
val_i = 8
|
|
185
210
|
# SEC-12: Bound min_value_length to reasonable range
|
|
186
211
|
if not 1 <= val_i <= 200:
|
|
187
212
|
print(f'[WARN] min_value_length={val_i} out of valid range (1-200), '
|
|
@@ -14,7 +14,7 @@ from pathlib import Path
|
|
|
14
14
|
from typing import TextIO
|
|
15
15
|
|
|
16
16
|
from . import __version__
|
|
17
|
-
from .utils import mask_secret
|
|
17
|
+
from .utils import mask_secret, sanitize_for_terminal
|
|
18
18
|
|
|
19
19
|
# ---------------------------------------------------------------------------
|
|
20
20
|
# ANSI color helpers (#31)
|
|
@@ -82,7 +82,10 @@ def print_report(
|
|
|
82
82
|
rel = Path(filepath).relative_to(root_path)
|
|
83
83
|
except ValueError:
|
|
84
84
|
rel = Path(filepath)
|
|
85
|
-
|
|
85
|
+
# SEC-36: Sanitise file path for terminal output to prevent
|
|
86
|
+
# escape-sequence injection via crafted filenames.
|
|
87
|
+
safe_rel = sanitize_for_terminal(str(rel))
|
|
88
|
+
print(_c(f' FILE: {safe_rel}', 'bold', use_color=color), file=stream)
|
|
86
89
|
print(f' {"─" * 60}', file=stream)
|
|
87
90
|
for finding in file_findings:
|
|
88
91
|
severity = finding.get('severity', 'medium')
|
|
@@ -91,9 +94,12 @@ def print_report(
|
|
|
91
94
|
# #2/#29 — mask the credential in the raw line display
|
|
92
95
|
masked_raw = _mask_in_line(finding['raw'], finding['full_value'])
|
|
93
96
|
|
|
97
|
+
# SEC-36: Sanitise type and raw line for terminal output.
|
|
98
|
+
safe_type = sanitize_for_terminal(finding['type'])
|
|
99
|
+
safe_raw = sanitize_for_terminal(masked_raw[:120])
|
|
94
100
|
sev_label = _c(f'[{severity.upper()}]', sev_color, use_color=color)
|
|
95
|
-
print(f' Line {finding["line"]:>4} {sev_label} [{
|
|
96
|
-
print(f' {
|
|
101
|
+
print(f' Line {finding["line"]:>4} {sev_label} [{safe_type}]', file=stream)
|
|
102
|
+
print(f' {safe_raw}', file=stream)
|
|
97
103
|
print(file=stream)
|
|
98
104
|
|
|
99
105
|
print(f'{"=" * 70}', file=stream)
|
|
@@ -142,13 +148,17 @@ def sarif_report(findings: list[dict], root: str) -> str:
|
|
|
142
148
|
results = []
|
|
143
149
|
|
|
144
150
|
for f in findings:
|
|
145
|
-
|
|
151
|
+
# SEC-35: Sanitise finding type for SARIF rule fields. The type can
|
|
152
|
+
# contain attacker-controlled content (e.g. xml-attr keys), so escape
|
|
153
|
+
# HTML to prevent XSS in downstream SARIF viewers.
|
|
154
|
+
safe_type = html.escape(f['type'])
|
|
155
|
+
rule_id = safe_type.replace(':', '-')
|
|
146
156
|
if rule_id not in rules:
|
|
147
157
|
rules[rule_id] = {
|
|
148
158
|
'id': rule_id,
|
|
149
|
-
'shortDescription': {'text':
|
|
159
|
+
'shortDescription': {'text': safe_type},
|
|
150
160
|
'fullDescription': {
|
|
151
|
-
'text': f'Credactor detected a potential hardcoded credential ({
|
|
161
|
+
'text': f'Credactor detected a potential hardcoded credential ({safe_type})',
|
|
152
162
|
},
|
|
153
163
|
'help': {
|
|
154
164
|
'text': ('Remove the hardcoded credential and use an'
|
|
@@ -105,8 +105,15 @@ def _is_safe_value(val: str, extra_safe: set[str] | None = None) -> bool:
|
|
|
105
105
|
if cleaned.startswith('${') and '}' in cleaned:
|
|
106
106
|
return True
|
|
107
107
|
if cleaned.startswith('$') and not cleaned.startswith('${'):
|
|
108
|
-
# Bare
|
|
109
|
-
|
|
108
|
+
# SEC-37: Bare $VAR — validate that the text after $ begins with a
|
|
109
|
+
# plausible POSIX env var name ([A-Za-z_][A-Za-z0-9_]*). Uses
|
|
110
|
+
# re.match (prefix) rather than fullmatch so that dynamic references
|
|
111
|
+
# with suffixes ($HOME/.aws/credentials, $TOKEN:prefix, $VAR-suffix)
|
|
112
|
+
# remain safe while pure non-identifier strings ($+foo, $/path,
|
|
113
|
+
# $123abc) are correctly rejected.
|
|
114
|
+
env_name = cleaned[1:]
|
|
115
|
+
if env_name and re.match(r'[A-Za-z_][A-Za-z0-9_]*', env_name):
|
|
116
|
+
return True
|
|
110
117
|
if cleaned.startswith('{%') and '%}' in cleaned:
|
|
111
118
|
return True
|
|
112
119
|
if cleaned.startswith('{{') and '}}' in cleaned:
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: credactor
|
|
3
|
+
Version: 2.3.3
|
|
4
|
+
Summary: Scan and redact hardcoded credentials in source code
|
|
5
|
+
Author: Rx
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Repository, https://github.com/rxb06/Credactor
|
|
8
|
+
Keywords: security,credentials,secrets,scanner,redactor
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Security
|
|
17
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: encoding
|
|
22
|
+
Requires-Dist: charset-normalizer>=3.0; extra == "encoding"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
25
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
[](https://pypi.org/project/credactor/)
|
|
29
|
+
[](https://github.com/rxb06/Credactor/actions/workflows/ci.yml)
|
|
30
|
+
[](LICENSE)
|
|
31
|
+
|
|
32
|
+
# Credactor
|
|
33
|
+
|
|
34
|
+
> **TL;DR:** Detect and redact hardcoded credentials before they hit version control. Regex + entropy + context-aware analysis, SARIF output, pre-commit hooks, parallel scanning, automated redaction.
|
|
35
|
+
|
|
36
|
+
Credactor scans source code for hardcoded secrets — API keys, tokens, passwords, private keys, connection strings — and replaces them with language-aware environment variable references before they reach version control. It runs as a CLI tool, a pre-commit hook, or in CI pipelines. SARIF output plugs straight into GitHub Code Scanning.
|
|
37
|
+
|
|
38
|
+
<img width="1280" height="640" alt="credactor" src="https://github.com/user-attachments/assets/f1f94a9c-feea-4b8b-9ea4-81f25f07c4df" />
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Why Credactor?
|
|
43
|
+
|
|
44
|
+
Most secret scanners stop at detection. Credactor redacts in place and replaces credentials with the right env var syntax for each language — `os.environ` in Python, `process.env` in JS, `System.getenv` in Java, and so on. It assigns severity levels so you can triage critical findings first instead of wading through noise.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install credactor
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
From source:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git clone https://github.com/rxb06/Credactor.git
|
|
56
|
+
cd Credactor
|
|
57
|
+
pip install -e .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
After either method, `credactor` works from any directory.
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
> Always run `--dry-run` first and review findings before redacting. False positives are possible — use `# credactor:ignore` or `.credactorignore` to suppress them.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Scan current directory (dry run)
|
|
68
|
+
credactor --dry-run .
|
|
69
|
+
|
|
70
|
+
# Scan and interactively redact
|
|
71
|
+
credactor .
|
|
72
|
+
|
|
73
|
+
# Redact everything without prompting
|
|
74
|
+
credactor --fix-all .
|
|
75
|
+
|
|
76
|
+
# CI mode — exit 1 on findings
|
|
77
|
+
credactor --ci .
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Pre-commit Hook
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
# .pre-commit-config.yaml
|
|
84
|
+
repos:
|
|
85
|
+
- repo: https://github.com/rxb06/Credactor
|
|
86
|
+
rev: v2.3.3
|
|
87
|
+
hooks:
|
|
88
|
+
- id: credactor
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or run as a module:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
python -m credactor .
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Detection
|
|
98
|
+
|
|
99
|
+
| Category | Examples | Severity |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| Cloud provider keys | AWS (`AKIA...`), GCP (`AIza...`), Stripe (`sk_live_...`), Slack (`xoxb-...`) | Critical |
|
|
102
|
+
| Platform tokens | GitHub (`ghp_`, `github_pat_`), GitLab (`glpat-`), npm (`npm_`), PyPI (`pypi-`) | Critical |
|
|
103
|
+
| Private keys | PEM blocks (`-----BEGIN RSA PRIVATE KEY-----`) | Critical |
|
|
104
|
+
| JWT tokens | `eyJ...` three-segment tokens | High |
|
|
105
|
+
| Connection strings | `postgresql://user:pass@host`, `mongodb+srv://...`, `redis://...` | High |
|
|
106
|
+
| Variable assignments | `password = "..."`, `api_key = "..."`, `db_password = "..."` | High/Medium |
|
|
107
|
+
| XML attributes | `<add key="Password" value="..." />` | High |
|
|
108
|
+
| High-entropy strings | Hex (32-64 chars), Base64 (60+ chars) | Medium/Low |
|
|
109
|
+
|
|
110
|
+
## Features
|
|
111
|
+
|
|
112
|
+
- Entropy-based detection with per-pattern thresholds to cut false positives
|
|
113
|
+
- Interactive or batch redaction — review one-by-one, or `--fix-all`
|
|
114
|
+
- Language-aware replacements (`os.environ`, `process.env`, `System.getenv`, etc.)
|
|
115
|
+
- Git history scanning via `--scan-history`
|
|
116
|
+
- `.bak` backups before any file modification
|
|
117
|
+
- Inline `# credactor:ignore` suppression and `.credactorignore` allowlists
|
|
118
|
+
- Per-repo config via `.credactor.toml`
|
|
119
|
+
- Parallel scanning (8 workers) for large repos
|
|
120
|
+
- SARIF 2.1.0 output with column-level annotations for GitHub Code Scanning
|
|
121
|
+
- `--fail-on-error` to catch files that couldn't be scanned
|
|
122
|
+
|
|
123
|
+
## Scanned File Types
|
|
124
|
+
|
|
125
|
+
`.py` `.js` `.ts` `.jsx` `.tsx` `.sh` `.bash` `.env` `.env.*` `.cfg` `.ini` `.toml` `.yaml` `.yml` `.rb` `.go` `.java` `.php` `.cs` `.kt` `.tf` `.hcl` `.conf` `.properties` `.xml`
|
|
126
|
+
|
|
127
|
+
JSON files are excluded by default (high false-positive rate from API responses). Use `--scan-json` to include them.
|
|
128
|
+
|
|
129
|
+
## Exit Codes
|
|
130
|
+
|
|
131
|
+
| Code | Meaning |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `0` | No findings, or all resolved |
|
|
134
|
+
| `1` | Unresolved findings |
|
|
135
|
+
| `2` | Error, or files skipped with `--fail-on-error` |
|
|
136
|
+
|
|
137
|
+
## Docs
|
|
138
|
+
|
|
139
|
+
| Document | Description |
|
|
140
|
+
|----------|-------------|
|
|
141
|
+
| [Setup Guide](docs/setup.md) | Installation, configuration, CI/CD integration |
|
|
142
|
+
| [User Guide](docs/user-guide.md) | CLI reference, replacement modes, backup safety |
|
|
143
|
+
| [Examples](docs/examples.md) | Common workflows with output |
|
|
144
|
+
| [Integration](docs/integration.md) | Pre-commit hooks, CI pipelines |
|
|
145
|
+
| [Security](docs/security.md) | Threat model, hardening measures, known limitations |
|
|
146
|
+
| [Changelog](CHANGELOG.md) | Version history |
|
|
147
|
+
| [Contributing](CONTRIBUTING.md) | Development setup, code style, PR process |
|
|
148
|
+
| [Disclaimer](docs/DISCLAIMER.md) | Limitations, safe usage, warranty |
|
|
149
|
+
|
|
150
|
+
## Licence
|
|
151
|
+
|
|
152
|
+
Apache 2.0. See [LICENSE](LICENSE).
|