This guide covers security considerations when implementing OpenCPX.
The /cpx endpoint should return references to evidence, not the evidence itself.
Don't do this:
{
"evidence": {
"iam_policies": [
{ "name": "admin", "permissions": [...] }
]
}
}Do this:
{
"evidence_refs": [
"https://evidence.company.com/iam-export.json"
]
}Presigned URLs should expire:
- Default: 1 hour
- Maximum: 24 hours
- Best practice: Generate fresh URLs per request
{
"evidence_refs": [{
"type": "presigned_url",
"url": "https://s3.aws.com/...?signature=...",
"expires": "2024-01-15T13:00:00Z"
}]
}- Only expose what's necessary
- Scope access to specific files, not buckets
- Use separate credentials for evidence access
For public compliance data (trust centers):
- No authentication required
- Expose only public information
- Rate limit to prevent abuse
// Public endpointmux.HandleFunc("/cpx", publicHandler)For sensitive or customer-specific data:
API Key:
funcprotectedHandler(w http.ResponseWriter, r*http.Request) {
apiKey:=r.Header.Get("X-API-Key")
if!validateAPIKey(apiKey) {
http.Error(w, "Unauthorized", 401)
return
}
// Serve posture
}OAuth/JWT:
funcprotectedHandler(w http.ResponseWriter, r*http.Request) {
token:=r.Header.Get("Authorization")
claims, err:=validateJWT(token)
iferr!=nil {
http.Error(w, "Unauthorized", 401)
return
}
// Use claims to filter posture
}For multi-tenant applications:
funccustomerHandler(w http.ResponseWriter, r*http.Request) {
customerID:=r.URL.Query().Get("customer_id")
// Verify customer has accessif!hasAccess(r.Context(), customerID) {
http.Error(w, "Forbidden", 403)
return
}
// Return customer-specific postureposture:=getPostureForCustomer(customerID)
}Use cloud provider presigned URLs:
AWS S3:
importboto3frombotocore.configimportConfigs3=boto3.client('s3', config=Config(signature_version='s3v4'))
url=s3.generate_presigned_url(
'get_object',
Params={
'Bucket': 'evidence-bucket',
'Key': 'soc2/report.pdf'
},
ExpiresIn=3600# 1 hour
)Google Cloud Storage:
fromgoogle.cloudimportstorageclient=storage.Client()
bucket=client.bucket('evidence-bucket')
blob=bucket.blob('soc2/report.pdf')
url=blob.generate_signed_url(
version='v4',
expiration=timedelta(hours=1),
method='GET'
)Include hashes for integrity verification:
{
"evidence_refs": [{
"url": "https://...",
"hash": "sha256:a1b2c3d4e5f6...",
"size_bytes": 1048576
}]
}Consumers should verify:
importhashlibdefverify_evidence(content, expected_hash):
actual_hash=hashlib.sha256(content).hexdigest()
returnf"sha256:{actual_hash}"==expected_hashLog all evidence access:
deflog_evidence_access(request, evidence_ref):
logger.info({
"event": "evidence_access",
"user": request.user,
"evidence_url": evidence_ref["url"],
"timestamp": datetime.utcnow().isoformat(),
"ip_address": request.remote_addr,
"user_agent": request.headers.get("User-Agent")
})- Framework compliance status
- Overall scores
- Audit dates and auditors
- Certificate references
- Public policy documents
- Detailed control evidence
- Internal policies
- Customer-specific data
- Security configurations
- Vulnerability reports
- Credentials or secrets
- Customer PII
- Internal IP addresses
- Unredacted security findings
Prevent abuse with rate limiting:
import"golang.org/x/time/rate"varlimiter=rate.NewLimiter(rate.Every(time.Second), 10)
funcrateLimitedHandler(w http.ResponseWriter, r*http.Request) {
if!limiter.Allow() {
http.Error(w, "Too Many Requests", 429)
return
}
// Serve posture
}Validate all input:
defget_posture(request):
format=request.args.get('format', 'json')
ifformatnotin ['json', 'yaml']:
abort(400, 'Invalid format')
customer_id=request.args.get('customer_id')
ifcustomer_idandnotcustomer_id.isalnum():
abort(400, 'Invalid customer_id')For endpoints like /cpx/{tenant}:
funchandler(w http.ResponseWriter, r*http.Request) {
tenant:=chi.URLParam(r, "tenant")
// Validate tenant ID formatif!isValidTenantID(tenant) {
http.Error(w, "Invalid tenant", 400)
return
}
// Check authorizationif!canAccessTenant(r.Context(), tenant) {
http.Error(w, "Forbidden", 403)
return
}
}Always use HTTPS:
- Enforce TLS 1.2+
- Use valid certificates
- Implement HSTS
- Redirect HTTP to HTTPS
funcmain() {
// Redirect HTTP to HTTPSgohttp.ListenAndServe(":80", http.HandlerFunc(redirectToHTTPS))
// Serve HTTPShttp.ListenAndServeTLS(":443", "cert.pem", "key.pem", handler)
}Configure CORS appropriately:
funccorsMiddleware(next http.Handler) http.Handler {
returnhttp.HandlerFunc(func(w http.ResponseWriter, r*http.Request) {
// Restrict to known domainsorigin:=r.Header.Get("Origin")
ifisAllowedOrigin(origin) {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
w.Header().Set("Access-Control-Allow-Methods", "GET")
w.Header().Set("Access-Control-Allow-Headers", "Authorization")
next.ServeHTTP(w, r)
})
}Add security headers:
funcsecurityHeaders(next http.Handler) http.Handler {
returnhttp.HandlerFunc(func(w http.ResponseWriter, r*http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Content-Security-Policy", "default-src 'none'")
w.Header().Set("Cache-Control", "no-store")
next.ServeHTTP(w, r)
})
}Maintain an audit trail:
defget_posture(request):
# Log the requestaudit_log.info({
"action": "cpx_endpoint_accessed",
"timestamp": datetime.utcnow().isoformat(),
"client_ip": request.remote_addr,
"user_agent": request.headers.get("User-Agent"),
"authenticated_user": getattr(request, "user", None),
"parameters": dict(request.args)
})
# Generate and return posturereturngenerate_posture()- Use HTTPS only
- Implement authentication for sensitive data
- Use presigned URLs for evidence
- Set short expiration times
- Include hash verification
- Validate all inputs
- Implement rate limiting
- Add security headers
- Log all access
- Never expose raw evidence in API
- Use time-limited access tokens
- Scope access to specific files
- Log evidence retrieval
- Verify integrity with hashes
- Monitor for unusual access patterns
- Alert on authentication failures
- Track evidence access
- Review logs regularly
If you find a security vulnerability in OpenCPX:
- Do not open a public issue
- Email security@opencpx.io
- Include detailed reproduction steps
- Allow time for a fix before disclosure
We follow responsible disclosure practices and will credit reporters.