Skip to content
GitHub

Security


This document covers security considerations for the lexigram core package — primarily the DI container, configuration system, and ambient capabilities.

ThreatRiskMitigation
Untrusted types injected into containerMediumContainer validates protocol conformance at registration time; only registered types are resolved
Secrets leaked in config dumpsHighLexigramConfig.to_dict(redact_secrets=True) redacts fields matching secret patterns (api_key, password, token)
Ambient capability abuse (clock, identity, hashing)Mediumuse() context manager is the only override mechanism; production code can’t accidentally replace ambient capabilities
Module visibility bypassLowbypass_visibility=True is only used in framework-internal paths; public API enforces visibility rules
Service locator anti-patternMediumContainer is never passed to services — only typed constructor parameters are injected
  • This document applies to the core container and configuration system only
  • Authentication/authorization is handled by lexigram-auth
  • Web security (CORS, CSRF, rate limiting) is handled by lexigram-web
  • Encryption and hashing are handled by lexigram.security.* (optional security extras)

Never store secrets in YAML files. Use environment variables or a secret store:

# ❌ Wrong — secret in YAML
# application.yaml:
# db:
# password: "super-secret-123"
# ✅ Correct — load from env var
import os
app.register_secrets({
"DATABASE_PASSWORD": os.environ["DATABASE_PASSWORD"],
})

All configuration can be overridden via LEX_ prefixed environment variables:

Terminal window
export LEX_DB__HOST="db.example.com"
export LEX_DB__PASSWORD="secret"

Lexigram validates secrets before any provider boots:

from lexigram import Application
from lexigram.config.secrets import SecretsPolicy
import os
app = Application("my-app")
app.register_secrets(
{
"JWT_SECRET": os.environ.get("JWT_SECRET", ""),
"OAUTH_CLIENT_SECRET": os.environ.get("OAUTH_CLIENT_SECRET", ""),
},
policy=SecretsPolicy(strict=True),
)

When strict=True, the application will fail to boot if any secret is empty or matches insecure patterns ("placeholder", "changeme").

config = LexigramConfig.from_yaml()
# Redact secrets for logging or debugging
safe_dict = config.to_dict(redact_secrets=True)
# Fields like db__password → "***"

Lexigram provides three ambient capabilities — process-level singletons that are exceptions to the DI rule:

CapabilityImportRisksSecure Usage
Clockfrom lexigram.primitives import clockTime-based attacks (prediction, replay)Use clock.now() for timestamps; inject FixedClock in tests
Identityfrom lexigram import identityUUID predictabilityidentity.new_uuid() uses uuid4() (random)
Hashingfrom lexigram import hashingWeak hash choicesDefault is SHA-256; hashing.hash_hex(data)

Test override only — never replace in production:

from lexigram.testing.clock import FixedClock
from lexigram.primitives import clock
with clock.use(FixedClock("2026-01-01")):
assert clock.now().year == 2026

The container validates that registered implementations conform to their declared protocol types:

container.singleton(CacheBackendProtocol, RedisCacheBackend)
# If RedisCacheBackend does not implement CacheBackendProtocol,
# the registration raises ProtocolValidationError

Once frozen, no new services can be registered:

container.freeze()
container.singleton(MyService, MyService()) # ContainerError

Override is only allowed in containers created with testing_mode=True:

container = Container(testing_mode=True)
container.freeze()
container.override(ServiceProtocol, FakeService()) # ✅ OK
container = Container(testing_mode=False)
container.freeze()
container.override(ServiceProtocol, FakeService()) # ❌ ContainerError

PracticeRationale
Never store secrets in YAMLYAML files are often committed to version control
Use register_secrets() with strict policyCatches missing or placeholder secrets at boot time
Redact config before loggingPrevents secret leakage in log aggregation systems
Pin dependenciesAlpha packages may change without notice; pin versions in production
Set LEX_QUIET=1 in productionSuppresses the startup banner which may leak version info
Use testing containersNever run production containers with testing_mode=True
Validate container before bootCatches misconfiguration early: container.validate()

Found a security vulnerability? Open an issue on GitHub with the label security.