Skip to main content
Version: Next 🚧

Policy Hot Reload

jGuard supports zero-downtime policy updates through hot reload. This allows security policies to be updated at runtime without restarting the JVM.

Overview​

Hot reload monitors external policy files and atomically swaps the active policy when changes are detected.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Runtime β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ PolicyLoader │───▢│ FileWatcher │───▢│ Atomic β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ (poll-based) β”‚ β”‚ Swap β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β–Ό β–Ό β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ policies/ β”‚ β”‚ Detect β”‚ β”‚ AtomicRef β”‚ β”‚
β”‚ β”‚ *.bin files β”‚ β”‚ Changes β”‚ β”‚ <Enforcer> β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

When to Use Hot Reload​

Enable Hot Reload When:​

ScenarioReason
Rapid incident responseBlock compromised capabilities without downtime
A/B policy testingTest policy changes on production traffic
Gradual rolloutsPhase in restrictions incrementally
Development/stagingIterate on policies without restarts
Multi-tenant environmentsUpdate tenant policies independently

Disable Hot Reload When:​

ScenarioReason
High-security environmentsPrevent runtime policy tampering
Immutable infrastructurePolicies baked into container images
Audit requirementsPolicy changes require restart/approval
Air-gapped systemsNo external policy directory needed
Performance-criticalEliminate file polling overhead

Configuration​

Gradle Plugin​

jguardPolicy {
// Enable hot reload
hotReload = true

// Poll interval in seconds (default: 5)
hotReloadInterval = 2

// External policy directories
externalPoliciesSourceDir = file("policies-src")
externalPoliciesOutputDir = file("policies")
}

System Properties​

java -Djguard.reload=true \
-Djguard.reload.interval=5 \
-Djguard.policy.override=/etc/myapp/policies \
-javaagent:jguard-agent.jar \
-jar app.jar
PropertyDefaultDescription
jguard.reloadfalseEnable hot reload
jguard.reload.interval5Poll interval in seconds
jguard.policy.overrideβ€”Directory for external policies

Example: Incident Response​

A vulnerability is discovered in a third-party library. Block its network access immediately without restarting.

Initial State​

The application is running with this external policy:

// policies-src/com.vendor.library.jguard
security module com.vendor.library {
entitle module to network.outbound("api.vendor.com", 443);
entitle module to fs.read("config", "**");
}

Incident Response​

  1. Edit the policy to deny network access:
// policies-src/com.vendor.library.jguard
security module com.vendor.library {
// INCIDENT: CVE-2024-XXXX - block all network
deny module to network.outbound;

entitle module to fs.read("config", "**");
}
  1. Compile the updated policy:
./gradlew compileExternalPolicies
  1. Within the poll interval, the agent detects the change:
[INFO] PolicyReloader - Change detected: com.vendor.library.bin
[INFO] PolicyReloader - Policy reloaded successfully
[INFO] PolicyReloader - Denied capabilities: network.outbound
  1. The library is now blocked from network access - no restart required.

Example: Gradual Restriction Rollout​

Restrict a library's capabilities incrementally to avoid breaking changes.

Week 1: Audit Mode​

security module legacy.library {
// Audit what the library actually uses
entitle module to network.outbound;
entitle module to threads.create;
entitle module to fs.read("data", "**");
}

Week 2: Restrict Network​

After audit shows the library only calls api.internal.com:

security module legacy.library {
// Restrict to observed usage
entitle module to network.outbound("api.internal.com", 443);
entitle module to threads.create;
entitle module to fs.read("data", "**");
}

Week 3: Restrict Threads​

After confirming thread usage pattern:

security module legacy.library {
entitle module to network.outbound("api.internal.com", 443);
// Remove thread creation - not actually needed
// entitle module to threads.create;
entitle module to fs.read("data", "**");
}

Example: Multi-Tenant Policy Updates​

Update policies for specific tenants without affecting others.

Directory Structure​

/etc/myapp/policies/
β”œβ”€β”€ _global.bin # Shared restrictions
β”œβ”€β”€ tenant.acme.bin # Acme Corp policies
β”œβ”€β”€ tenant.globex.bin # Globex policies
└── tenant.initech.bin # Initech policies

Update Single Tenant​

// policies-src/tenant.acme.jguard
security module tenant.acme {
// Acme requested additional network access
entitle module to network.outbound("*.acme.com", 443);
entitle module to network.outbound("api.partner.com", 443); // NEW
entitle module to fs.read("tenants/acme", "**");
}

Compile and the change affects only Acme's module.

Error Handling​

Compile-Time Validation​

Syntax errors are caught during compilation, before they can affect the running system:

$ ./gradlew compileExternalPolicies

> Task :compileExternalPolicies FAILED
Error: policies-src/com.vendor.library.jguard:5:12
Unknown capability: network.inbound
Did you mean: network.listen?

The agent continues using the previous valid policy.

Runtime Error Handling​

ScenarioBehavior
Corrupted .bin fileKeep previous policy, log warning
Missing fileKeep previous policy, log warning
Invalid policyReject at compile time
File permission errorKeep previous policy, log error

Log Output​

[INFO]  PolicyReloader - Watching: /etc/myapp/policies
[INFO] PolicyReloader - Poll interval: 5 seconds
[INFO] PolicyReloader - Change detected: com.vendor.library.bin
[INFO] PolicyReloader - Validating new policy...
[INFO] PolicyReloader - Policy reloaded successfully
[WARN] PolicyReloader - File corrupted, keeping previous: tenant.acme.bin

Atomic Swap Guarantee​

Policy updates are atomic - there's no window where an incomplete policy is active:

// Internal implementation uses AtomicReference
private final AtomicReference<PolicyEnforcer> enforcer = new AtomicReference<>();

void reload(PolicyEnforcer newEnforcer) {
// Atomic swap - no partial state
enforcer.set(newEnforcer);
}

In-flight operations complete with the old policy. New operations use the new policy.

Security Considerations​

Protecting the Policy Directory​

The external policy directory is a security-sensitive location:

# Restrict write access
chmod 750 /etc/myapp/policies
chown root:security /etc/myapp/policies

# Consider SELinux/AppArmor policies
# Only allow the deployment system to write

Disabling Hot Reload in Production​

For high-security environments, disable hot reload entirely:

jguardPolicy {
hotReload = false
// Policies baked into signed JARs only
}

Or via system property:

java -Djguard.reload=false -javaagent:jguard-agent.jar -jar app.jar

Immutable Container Deployments​

For container deployments with immutable infrastructure:

# Dockerfile
FROM eclipse-temurin:21-jre

# Bake policies into image
COPY policies/*.bin /app/policies/

# Disable hot reload - policies are immutable
ENV JAVA_OPTS="-Djguard.reload=false"

COPY app.jar /app/
ENTRYPOINT ["java", "-javaagent:jguard-agent.jar", "-jar", "/app/app.jar"]

Audit Trail​

For compliance, log all policy changes:

# Monitor policy directory for changes
inotifywait -m -e modify,create,delete /etc/myapp/policies/ | \
while read event; do
echo "$(date) POLICY_CHANGE: $event" >> /var/log/security/policy-audit.log
done

Performance Impact​

ConfigurationOverhead
Hot reload disabledNone
Hot reload enabled (5s interval)~1ms per interval (file stat)
Hot reload enabled (1s interval)~1ms per interval

The overhead is minimal - just file modification time checks, not file reads.

Best Practices​

  1. Use short intervals for development (1-2 seconds)
  2. Use longer intervals for production (30-60 seconds) if enabled
  3. Disable in high-security environments where policy changes require restart
  4. Protect the policy directory with appropriate permissions
  5. Monitor policy changes for audit and alerting
  6. Test policy changes in staging before production hot reload
  7. Keep previous policy versions for rollback

Next Steps​