Security Dossier (CISO)¶
Classification: Restricted β CISO / Security Team
Version: 1.1
Date: 2026-05-23
Audience: CISO, Security Officers, Security Engineering
Security overview¶
Repod is an enterprise-grade Linux package repository manager (APT and RPM editions). It provides full control over the software supply chain, from package intake to distribution to target systems.
Security principles¶
- Defense in depth β every incoming package traverses a multi-stage, independent control pipeline (antivirus β CVE β GPG β integrity).
- Least privilege β five distinct roles with precisely scoped permission boundaries.
- Separation of duties β CVE remediation decisions are reserved for the security team; operators cannot self-approve vulnerable packages.
- Full traceability β every sensitive action is recorded in append-only JSONL audit files that cannot be modified or deleted via the API.
- Regulatory alignment β NIS2 (EU 2022/2555), ANSSI SecNumCloud, GDPR.
Core components¶
| Component | Technology | Role |
|---|---|---|
| Backend API | FastAPI / Python 3.11 | Business logic, authentication, security pipeline |
| Database | SQLite (WAL mode) | Users, packages, tokens, CVE records |
| Antivirus | ClamAV 1.4.3 | Malware detection on every upload |
| CVE scanner | Grype v0.112.0 (Anchore) | Known vulnerability analysis |
| SBOM generator | Syft v1.44.0 | CycloneDX 1.5 + SPDX 2.3 per package |
| Frontend | React + Nginx | CISO / operator interface |
| Storage | Docker volume /repos |
Packages, manifests, audit logs, GPG keyrings |
Authentication and identity management¶
Local authentication¶
- Password storage: bcrypt via
passlib[bcrypt]. No plaintext passwords stored anywhere. - Password policy (enforced server-side):
- Minimum 8 characters
- At least 1 uppercase letter AND at least 1 digit or special character
- Password reset: tokens stored as SHA-256 hashes only. Valid for 30 minutes.
- Account status:
is_activeverified on every authenticated request. Deactivating an account immediately invalidates all active sessions.
JWT tokens (user sessions)¶
| Property | Value |
|---|---|
| Algorithm | HS256 |
| Lifetime | 60 minutes (configurable via JWT_EXPIRE_MINUTES) |
| Secret key validation | Application refuses to start if JWT_SECRET_KEY is empty or a default value when ENV=production |
| Per-request check | Active status verified on every request, not only at token creation |
Known limitation β no JWT revocation
A valid token remains functional until natural expiry (60 min) even after explicit logout. Compensating control: the 60-minute window limits exploitation exposure; account deactivation is checked on every request.
API tokens (CI/CD)¶
| Property | Value |
|---|---|
| Format | repod_ prefix + cryptographically secure random suffix |
| Storage | SHA-256 hash only β plaintext never persisted |
| Expiry | Configurable per token (optional) |
| Revocation | Immediate, by admin or token owner |
LDAP / Active Directory (optional)¶
| Property | Value |
|---|---|
| Library | ldap3 |
| TLS | Certificate verification enabled by default (verify_cert=True) |
| Auto-provisioning | Local account created on first successful LDAP login with a random, non-usable local password |
| Bind password masking | Masked as *** in GET /settings responses |
Rate limiting¶
| Endpoint | Limit |
|---|---|
Authentication (/auth/*) |
10 req / min |
| Package upload | 20 req / min |
| Import / fetch | 10 req / min |
| Batch operations | 5 req / min |
| Repository sync | 3 req / min |
Access control (RBAC)¶
Five privilege levels with precisely scoped permissions:
| Permission | admin | maintainer | uploader | auditor | reader |
|---|---|---|---|---|---|
| User management | β | β | β | β | β |
| Settings modification | β | β | β | β | β |
| Package upload | β | β | β | β | β |
| Package import | β | β | β | β | β |
| Package promotion / deletion | β | β | β | β | β |
| Repository sync | β | β | β | β | β |
| CVE decision (approve/reject) | β | β | β | β | β |
| Audit log access | β | β | β | β | β |
| Package read access | β | β | β | β | β |
| SBOM export | β | β | β | β | β |
| API token management | β (all) | β (own) | β (own) | β | β |
admin β full platform access. Only role that can manage users, change system settings, and configure CVE policies.
maintainer β full package lifecycle. Can approve/reject CVE-flagged packages. Cannot manage users or system settings.
uploader β upload and import packages only. Designed for CI/CD pipelines requiring minimal access.
auditor β read-only access to packages and audit logs. Suitable for compliance teams and external auditors.
reader β read-only access to packages only. No audit log access.
Package security pipeline¶
Every incoming package traverses a 6-stage sequential pipeline before publication.
flowchart TD
A([Package received]) --> B[Stage 1\nFormat validation]
B -->|FAIL| R1([Rejected β HTTP 400])
B --> C[Stage 2\nSHA-256 provenance check]
C -->|MISMATCH| R2([Rejected])
C --> D[Stage 3\nClamAV antivirus scan]
D -->|VIRUS| Q1([Quarantined])
D --> E[Stage 4\nGrype CVE analysis\n+ EPSS + CISA KEV]
E -->|policy=block| Q2([Quarantined])
E -->|policy=review| PR([pending_review\nCISO queue])
E --> F[Stage 5\nGPG signature verification]
F -->|INVALID| R3([Rejected])
F --> G[Stage 6\nDependency check]
G --> PUB([Published to repository])
Stage-by-stage description¶
Stage 1 β Format validation Verifies structural integrity of the package file:
dpkg-deb --info β rejects malformed, truncated, or non-compliant .deb files.
rpm -qp --info β rejects malformed or corrupt .rpm files.
Stage 2 β SHA-256 provenance check
Compares the uploaded package hash against the source repository index. Any
mismatch results in immediate rejection.
Stage 3 β ClamAV antivirus scan
Submitted to ClamAV (signature database updated daily by freshclam). Malware
detection triggers quarantine and blocks publication.
Stage 4 β Grype CVE scan
Grype cross-references the package SBOM against NVD, GitHub Advisory Database,
and CISA KEV. Response is determined by the configured policy:
| Severity | Policy | Behavior |
|---|---|---|
| Critical | block |
Quarantined β never published |
| Critical | review |
pending_review β mandatory CISO queue |
| High | block |
Quarantined |
| High | review |
pending_review β CISO queue |
| Medium | warn |
Published with warning flag |
| Low | allow |
Published without restriction |
Policies are configurable per-severity by administrators.
Stage 5 β GPG signature verification
If a detached signature is present, it is verified against the keyring. A
present-but-invalid signature is a hard failure. An absent signature is a soft
pass (not all packages carry detached signatures).
Stage 6 β Dependency availability check
Verifies declared dependencies against the internal pool. Missing dependencies
generate a warning (non-blocking by default; set strict_deps=true for
air-gapped environments).
Vulnerability management (CVE)¶
CISO review queue¶
Packages where at least one CVE triggers the review policy enter pending_review
status. They are visible in the CISO interface but inaccessible to repository
consumers until a decision is made.
Available actions (roles: admin, maintainer):
- Approve β package published; justification mandatory and recorded.
- Reject β package moved to quarantine; justification mandatory and recorded.
SLA by severity¶
| Severity | Default SLA | Configurable |
|---|---|---|
| Critical | 0 days (immediate decision required) | Yes |
| High | 30 days | Yes |
| Medium | 90 days | Yes |
| Low | No SLA | Yes |
SLA breaches surface as alerts in the admin interface. Email and webhook notifications are available for SLA breach events.
CVE contextual enrichment¶
Each CVE in the CISO review queue displays:
- CVSS v3 score (base + attack vector)
- EPSS score (30-day exploitation probability)
- CISA KEV catalog membership
- Affected package, version, and available fix
- Previous decision justification (if re-reviewed)
SBOM and software traceability¶
Supported formats¶
| Standard | Version | Body |
|---|---|---|
| CycloneDX | 1.5 | OWASP |
| SPDX | 2.3 | ISO/IEC 5962:2021 |
SBOM contents¶
Each generated SBOM contains:
- Complete component inventory (direct and transitive dependencies)
- Associated CVE identifiers and their triage status
- SHA-256 hash of each component
- Vendor metadata, version, SPDX license identifier
- Generation timestamp and last CVE scan timestamp
Regulatory alignment¶
| Framework | Article / Section | Coverage |
|---|---|---|
| NIS2 (EU 2022/2555) | Article 21 β supply chain security | Software inventory, vulnerability management |
| ANSSI SecNumCloud | Software inventory | Component traceability |
| Executive Order 14028 (US) | SBOM for delivered software | Reference only |
SBOM API¶
# Per-package SBOM (CycloneDX)
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8000/sbom/nginx/1.24.0?format=cyclonedx&arch=amd64"
# Full repository SBOM
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8000/sbom/export?format=cyclonedx"
Audit trail¶
Format and storage¶
| Property | Value |
|---|---|
| Format | JSONL (JSON Lines) β one event per line |
| Organization | One file per day: /repos/audit/YYYY-MM-DD.jsonl |
| Access | Read-only; roles admin, maintainer, auditor |
| Write mode | Append-only β no modification or deletion API |
| Retention | Configurable (default: 90 days) |
Event structure¶
{
"timestamp": "2026-05-23T14:32:01.412000+00:00",
"action": "UPLOAD",
"user": "john.doe",
"result": "SUCCESS",
"package": "nginx",
"version": "1.24.0",
"detail": "status=pending_review | cve_findings=2"
}
Logged event types¶
| Event | Trigger |
|---|---|
LOGIN |
Authentication attempt β result includes source IP |
USER_CREATE |
Account creation (local or LDAP auto-provision) |
USER_UPDATE |
Account modification (role, status, email) |
USER_DELETE |
Account deletion |
PASSWORD_CHANGE |
User-initiated password change |
PASSWORD_RESET |
Admin-initiated or token-based password reset |
UPLOAD |
Package upload (pipeline results included) |
VALIDATE |
Validation pipeline failure detail |
DELETE |
Package deletion |
IMPORT |
Import from an external repository |
SYNC |
Repository index synchronization |
SECURITY_DECISION |
CVE approve/reject decision with justification |
RESCAN |
Package re-scanned for CVEs |
CLAMAV_UPDATE |
Antivirus signature database update |
INIT_DISTS |
Distribution initialized |
GDPR note¶
Audit logs include user IP addresses (personal data under GDPR). Retention must be aligned with the organization's internal data retention policy. Legal basis for processing: legitimate interest / NIS2 legal obligation.
Infrastructure hardening¶
Containerization¶
| Measure | Status | Detail |
|---|---|---|
| Docker socket not mounted | β | Backend has no access to /var/run/docker.sock |
| Source code not mounted in production | β | Only /repos volume mounted |
| Non-root user | β | Backend runs as appuser (UID 1000) |
| GPG via shared volume | β | /repos/gnupg shared without Docker socket |
FastAPI application¶
| Measure | Status | Detail |
|---|---|---|
| Swagger UI disabled in production | β | ENV=production β /docs returns 404 |
| Hot-reload disabled | β | uvicorn starts without --reload |
| JWT secret validation at startup | β | Refuses to start with default/empty key |
Secret masking in /settings |
β | SMTP and LDAP passwords masked as *** |
HTTP security headers¶
| Header | Value |
|---|---|
X-Frame-Options |
SAMEORIGIN |
X-Content-Type-Options |
nosniff |
X-XSS-Protection |
1; mode=block |
Referrer-Policy |
strict-origin-when-cross-origin |
Content-Security-Policy |
default-src 'self'; script-src 'self' 'unsafe-inline'; β¦ |
Permissions-Policy |
camera=(), microphone=(), geolocation=(), payment=() |
CSP unsafe-inline
'unsafe-inline' is present in script-src and style-src due to React's
use of inline styles. Migration to a nonce-based CSP is planned for v3.
Known limitations and compensating controls¶
No JWT revocation¶
| Risk | Medium |
| Description | JWT tokens remain valid until expiry (60 min) after logout or deactivation |
| Compensating control | Account deactivation is verified on every request; 60-minute window limits exposure |
| Planned fix | Redis-based token blacklist in v2.x |
No HTTPS in default configuration¶
| Risk | High (if deployed without reverse proxy) |
| Description | TLS termination is delegated to a reverse proxy |
| Compensating control | Mandatory deployment behind Nginx / Traefik / Caddy |
| Documentation | Reverse proxy guide β |
Backend port exposed on all interfaces¶
| Risk | Medium (without reverse proxy) |
| Description | Port 8000 bound to all interfaces by default |
| Compensating control | Set BIND_HOST=127.0.0.1 in .env + firewall rules |
CSP with unsafe-inline¶
| Risk | Low (internal interface, no user-controlled script input) |
| Planned fix | Nonce-based CSP in v3 |
Compliance checklist¶
NIS2 (EU 2022/2555)¶
| Requirement | Status | Implementation |
|---|---|---|
| Supply chain security | β | CVE pipeline, SBOM, GPG signing, antivirus |
| Vulnerability management | β | Grype, CISO queue, configurable SLA, EPSS, CISA KEV |
| Logging and monitoring | β | JSONL audit trail, configurable retention, 19 event types |
| Access control | β | 5-role RBAC, JWT, API tokens |
| Incident handling | β οΈ | Audit trail present β internal IR procedure required at org level |
| Encryption in transit | β οΈ | Delegated to reverse proxy |
| Business continuity | β | To be defined at organization level |
| Security testing | β | Penetration test / code review to be scheduled |
ANSSI SecNumCloud¶
| Requirement | Status | Implementation |
|---|---|---|
| Software inventory | β | CycloneDX 1.5 + SPDX 2.3 per package |
| Audit logs | β | Append-only JSONL, configurable retention |
| Access control | β | RBAC, least privilege |
| Environment separation | β οΈ | Docker containers β network isolation to be reinforced |
| Encryption at rest | β | /repos volume unencrypted β manage at OS/infrastructure level |
| Key management | β οΈ | Integrated GPG, validated JWT secret β HSM not included |
GDPR¶
| Requirement | Status | Implementation |
|---|---|---|
| Data minimization | β | Only email, role, and IP address collected |
| Data retention | β | Configurable (default 90 days) |
| Data security | β | bcrypt hashing, restricted access |
| Records of processing activities | β | To be documented by the organization |
Vulnerability reporting¶
Security vulnerabilities must be reported through a confidential channel.
Contact: security@[organization]
Channel: Do not use the public issue tracker.
Response SLA: 72 hours for acknowledgment β 30 days for remediation
of High/Critical findings.