The Session class is an abstract session management system for WackoWiki that extends ArrayObject to provide secure, configurable session handling. It implements sophisticated security features including session ID regeneration, anti-replay protection, nonce verification, and user agent/IP validation.
Location:src/class/session.php Type: Abstract class (must be extended with a SessionStoreInterface implementation) Inheritance:ArrayObject
Inactive ($active = false): Session not yet started or has been closed
Active ($active = true): Session is running and can store/retrieve data
Regenerated: Session ID has been replaced (tracked via $regenerated flag)
Session Data Storage
Session data is stored as an array accessible through ArrayObject interface:
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)<!markup:2:end>
$session['user_id'] = 123; // Set data
echo $session['user_id']; // Get data
==== Sticky Data ====
Variables prefixed with ##sticky_## are persistent across session resets:
- ##sticky__created##: Session creation timestamp
- ##sticky__flash##: Flash data lifetime tracking
- ##sticky__log##: Regeneration event log
- ##sticky__ip##: IP change tracking
==== Internal Tracking Variables ====
Variables prefixed with ##__## are internal session metadata:
- ##__started##: Session start time
- ##__updated##: Last session update time
- ##__regenerated##: Last session ID regeneration time
- ##__user_agent##: Client user agent string
- ##__user_ip##: Client IP address
- ##__user_tls##: TLS/SSL status
- ##__nonces##: Active nonce storage
- ##__expire##: Session expiration time (for old sessions)
----
=== Architecture ===
==== Class Hierarchy ====
$session->cf_gc_probability=2;// Probability of GC on shutdown (-1)$session->cf_gc_maxlifetime=144;// Max session file lifetime (seconds)
Cookie Settings
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
$session->cf_cookie_prefix = ''; // Prefix for all cookies
$session->cf_cookie_persistent = false; // Make cookies persistent
$session->cf_cookie_lifetime = ; // Cookie lifetime ( = session cookie)
$session->cf_cookie_path = '/'; // Cookie path
$session->cf_cookie_domain = ''; // Cookie domain ('' = current host)
$session->cf_cookie_secure = false; // HTTPS only
$session->cf_cookie_httponly = true; // Disable JavaScript access
$session->cf_cookie_samesite = COOKIE_SAMESITE; // SameSite attribute
===== Cache Control =====
<!--markup:1:begin-->
php<!markup:1:end> PHP
<!--markup:2:end-->$session->cf_cache_limiter='none';// Cache control mode (public|private|nocache|none)$session->cf_cache_expire=18*6;// Cache TTL (seconds)$session->cf_cache_mtime=;// Modify time for Last-Modified header
// Convert to array
$all_data = $session->toArray();
==== Session ID Management ====
<!--markup:1:begin-->
php<!markup:1:end>
PHP
<!--markup:2:end-->// Get current session ID$id=$session->id();// Returns: e.g., "abc123xyz..."// Get session name$name=$session->name();// Returns: 'myapp'// Get session ID from request$session->start('myapp',$_REQUEST['sid']??null);
Session State
PHP
// Check if session is activeif($session->active()){// Session is running}// Get last state change message$message=$session->message();// 'replay', 'ip', 'ua', 'timeout', etc.// Restart session (destroy old + start new)$session->restart();
Security Features
1. Session ID Regeneration
Purpose: Prevent session fixation attacks
Automatic Triggers:
Initial session creation (regenerated = 2)
First request after creation (regenerated = 1)
Periodic forced regeneration (based on cf_regen_time and cf_regen_probability)
**Parameters:**
- ##$delete_old##:
- ##false## (): Keep old session active for ~5 seconds (for pending AJAX requests)
- ##true## (1): Keep old session for time specified (unused in current code)
- ##2##: Immediately destroy old session
**Implementation Details:**
- New session ID is generated via ##store_generate_id()##
- Old session data is copied to new ID
- Old session marked with ##__expire## timestamp
- Cookie immediately updated with new ID
- Single regeneration per request (checked via ##$this->regenerated## flag)
- Logged in ##sticky__log## for debugging (max 15 entries)
(hl php)
// Example: Force regeneration on login
$session->start('myapp');
if ($user_authenticated) {
==== 2. User Agent Validation ====
**Purpose:** Detect browser/device changes that might indicate hijacking
**Behavior:**
- Stores user agent on first request
- Compares on subsequent requests using ##similar_text()##
- Destroys session if similarity < 95%
- Useful against bot attacks or stolen sessions
**Configuration:**
<!--markup:1:begin-->
php<!markup:1:end>** PHP
<!--markup:2:end-->// Automatic on each request (if enabled in code logic)// Triggers session destruction if UA changes significantly
3. IP Address Validation
Purpose: Detect IP spoofing or hijacking
Behavior:
Stores IP on first request
Compares on subsequent requests
Soft failure on mismatch: destroy = 1 (keeps regenerating)
Tracks IP changes in sticky__ip
Configuration:
<!markup:1:begin>
php<!--markup:1:end-->**
<!--markup:2:begin-->
(hl php)
$session->cf_ip = $_SERVER['REMOTE_ADDR']; // Set by HTTP class
// Validation happens automatically during start()
**IP Change Tracking:**
<!--markup:1:begin-->
php<!markup:1:end>** PHP
<!--markup:2:end-->// Access IP change history$ip_history=$session->sticky__ip;// Array of [ip => change_count]
4. TLS/SSL Validation
Purpose: Prevent protocol downgrade attacks
Behavior:
Checks if connection transitioned from HTTPS to HTTP
Destroys session on mismatch
Configuration:
<!markup:1:begin>
php<!--markup:1:end-->**
<!--markup:2:begin-->
(hl php)
$session->cf_tls = !empty($_SERVER['HTTPS']); // Set by HTTP class
// Validation happens automatically during start()
Request 1: Generate nonce, send in cookie
Request 2: Client sends nonce back, verify & generate new one
Request 3: If old nonce used again → reject (replay detected)
<!--markup:2:end-->$session->cf_referer_check='example.com';// Session rejected if HTTP_REFERER doesn't contain this string
API Reference
Public Methods
Lifecycle Management
start($name = null, $id = null): bool
Start or resume a session.
Parameters:
$name (string|null): Session name (cookie name base). Alphanumeric + underscore/dash. Defaults to 'sesid'
$id (string|null): Existing session ID to resume. If null, attempts to read from cookie
Returns:true if session started successfully, false on error
Side Effects:
Sets headers (cookies, cache control)
Populates session data from storage
Performs security validations
May trigger session ID regeneration
Example:
<!markup:1:begin>
php<!--markup:1:end-->**
<!--markup:2:begin-->
(hl php)
if ($session->start('webapp', $_COOKIE['sess_id'] ?? null)) {
// Session ready
} else {
// Session failed
}
**Validation Steps:**
1. Reject if headers already sent
2. Validate session name format
3. Retrieve ID from parameter or cookie
4. Check Referer header (if configured)
5. Validate ID format via ##store_validate_id()##
6. Read session data from storage
7. Verify nonces and timestamps
8. Check user agent, IP, TLS
9. Regenerate if needed
----
====== ##write_close(): void## ======
Save session data and close session.
**Side Effects:**
- Calls ##write_session()## to serialize and store data
- Calls ##store_close()## to close storage handler
- Sets ##$active = false##
**Example:**
(hl php)
$session['key'] = 'value';
$session->write_close(); // Ensure data is saved
----
====== ##restart(): bool## ======
Destroy current session and create new one.
**Equivalent to:** ##regenerate_id(true) + clean_vars() + populate()##
**Returns:** ##true## on success, ##false## on error
**Use Cases:**
- User logout and new login
- Security reset
- Complete session refresh
**Example:**
(hl php)
$session->restart();
// New session created, old data cleared, sticky_ vars preserved
----
===== Session Access =====
====== ##id(): mixed## ======
Get current session ID.
**Returns:** Session ID string or null if not started
(hl php)
$sid = $session->id(); // "abc123xyz..."
----
====== ##name(): string## ======
Get session name (cookie prefix).
**Returns:** Session name
<!--markup:1:begin-->
----
====== ##get_cookie($name)## ======
Retrieve cookie value.
**Parameters:**
- ##$name##: Cookie name (prefix automatically added)
**Returns:** Cookie value or null if not set
<!--markup:1:begin-->
----
===== ##store_write($id, $data): void## =====
Write session data to storage.
**Subclass must implement**
**Parameters:**
- ##$id##: Session ID
- ##$data##: Serialized session data (already processed by ##Ut::serialize()##)
**Example:**
(hl php)
protected function store_write($id, $data): void {
file_put_contents("/tmp/sess_$id", $data);
}
----
===== ##store_close(): void## =====
Close session storage.
**Subclass must implement** - Release resources
**Example:**
<!--markup:1:begin-->
php<!markup:1:end>** PHP
<!--markup:2:end-->protectedfunctionstore_close():void{// Close database, file, etc.}
store_gc(): void
Perform garbage collection on old sessions.
Subclass must implement - Delete expired sessions
Called During:
Shutdown handler (probabilistic, based on cf_gc_probability)
----
==== Private Methods (Internal Use) ====
====== ##populate(): void## ======
Initialize session tracking variables on first request.
**Called by:** ##start()##, ##restart()##
**Initializes:**
- ##__started##: Current timestamp
- ##__regenerated##: Current timestamp
- ##__user_agent##: Browser user agent
- ##__user_ip##: Client IP (if configured)
- ##__user_tls##: TLS status (if configured)
- ##sticky__created##: Creation time (if not exists)
----
====== ##write_session(): void## ======
Serialize and write session data to storage.
**Called by:** ##regenerate_id()##, ##write_close()##, ##terminator()##
**Updates:**
- ##__updated##: Current timestamp
- Calls ##store_write()## with serialized data
----
====== ##clean_vars(): void## ======
Remove non-sticky session variables.
**Called by:** ##restart()##, session validation failure
**Preserves:** Variables starting with ##sticky_##
----
====== ##prevent_replay(): void## ======
Generate and send anti-replay nonce.
**Called by:** ##populate()##
**Action:**
- Creates 'NoReplay' nonce
- Sends in cookie: ##{cf_cookie_prefix}NoReplay##
----
====== ##cache_limiter(): void## ======
Set HTTP cache control headers based on configuration.
**Called by:** ##start()## after session data loaded
**Modes:**
- ##'public'##: Cacheable, ##Cache-Control: public, max-age=...##
- ##'private'##: Private, ##Cache-Control: private, max-age=...##
- ##'private_no_expire'##: Private no TTL
- ##'nocache'##: No storage, ##Cache-Control: no-store##
- ##'none'##: No headers (default)
----
====== ##set_new_id(): void## ======
Generate and assign new session ID, send in cookie.
**Called by:** ##regenerate_id()##, ##start()## (for new sessions)
----
====== ##remove_cookie($cookie): void## ======
Remove existing Set-Cookie header to avoid duplicates.
**Called by:** ##setcookie()## before setting new value
----
====== ##nonce_index($action, $code): string## (static) ======
Generate storage key for nonce.
**Returns:** ##{action}.{base64_encoded_hash}##
----
----
=== Session Lifecycle ===
==== Complete Session Flow ====
┌─ Browser Request
│
├─ Application Code
│ └─ $session->start('appname')
│ │
│ ├─ Check if headers sent
│ ├─ Validate/read session name
│ ├─ Get session ID from:
│ │ 1. Parameter $id
│ │ 2. Cookie: {prefix}appname
│ ├─ Validate referer (if cf_referer_check set)
│ ├─ Validate ID format via store_validate_id()
│ ├─ store_open(name)
│ ├─ store_read(id)
│ │ └─ If missing/invalid/expired:
│ │ └─ set_new_id()
│ │ └─ regenerate_id = 2 (NEW)
│ ├─ Deserialize session data
│ ├─ exchangeArray(data)
│ ├─ active = true
│ ├─ cache_limiter()
│ │
│ └─ Security Checks (if NOT first request):
│ ├─ Verify NoReplay nonce
│ ├─ Check expiration flags
│ ├─ Check max session time
│ ├─ Check max idle time
│ ├─ Compare user agent (95%+ similarity)
│ ├─ Compare TLS status
│ ├─ Compare IP address
│ │ ├─ Match: OK
│ │ └─ Mismatch: destroy=1, regenerate
│ └─ Check regen time/probability
│ └─ regenerate_id()
│
├─ Application Code
│ └─ $session['key'] = 'value'
│
└─ End of Request
│
└─ register_shutdown_function() → terminator()
│
├─ Process flash data
│ └─ Decrement lifetimes
│ └─ Remove expired flash
├─ write_session()
│ └─ store_write(id, serialized_data)
├─ store_close()
├─ Probabilistic garbage collection
│ └─ store_gc() (cf_gc_probability % chance)
│ └─ Delete old sessions
└─ Output sent to browser
==== First Request (New Session) ====
start() is called
├─ No ID in cookie
├─ store_read(id) → false
├─ set_new_id()
│ └─ id = store_generate_id()
│ └─ send_cookie(name, id)
├─ data = []
├─ active = true
├─ populate()
│ ├─ __started = now
│ ├─ __regenerated = now
│ ├─ __user_agent = UA
│ └─ sticky__created = now
└─ return true
==== Subsequent Request (Resume Session) ====
start() is called
├─ ID from cookie
├─ store_read(id) → serialized_data
├─ data = unserialize(data)
├─ exchangeArray(data)
├─ active = true
├─ Security checks:
│ ├─ Replay check
│ ├─ Timeout checks
│ ├─ UA/IP/TLS checks
│ └─ May trigger regenerate_id()
└─ return true
==== Session ID Regeneration ====
regenerate_id($delete_old, $message) is called
├─ Check not headers_sent()
├─ Check $active
├─ Check not already regenerated in this request
├─ write_session() [Save current data]
├─ set __expire:
│ ├─ if $delete_old=: __expire = now + 5
│ └─ if $delete_old>: __expire =
├─ Generate new ID:
│ └─ loop:
│ ├─ id = store_generate_id()
│ └─ while store_read(id) !== false [Ensure unique]
├─ Lock new session: store_read(id, true)
├─ Set: __regenerated = now
├─ Set: regenerated = 1
├─ Log event: sticky__log[] = [now, message]
└─ return true
----
=== Flash Data ===
Flash data persists for a limited number of requests (typically 1-2) and is automatically removed.
==== Usage ====
(hl php)
// Store flash message for next request
$session->set_flash('error', 'Username already exists', 1); // 1 request
$session->set_flash('info', 'Welcome back!', 2); // 2 requests
// In next request, data automatically available
echo $session['error']; // "Username already exists"
==== How It Works ====
1. **Storage:** Flash data stored in ##$session->sticky__flash##
- Key: Variable name
- Value: Lifetime in requests
2. **Cleanup:** In ##terminator()## (shutdown handler):
3. **Persistence:** Flash variables are kept in ##sticky__flash## even during session resets
==== Example: Login Flow ====
<!--markup:1:begin-->
php<!markup:1:end-->
PHP
<!--markup:2:end-->// POST /loginif($credentials_valid){$session->restart();// New session$session['user_id']=$user->id;$session->set_flash('success','Login successful!',1);header('Location: /dashboard');}else{$session->set_flash('error','Invalid credentials',1);header('Location: /login');}// GET /dashboard (or /login on failure)if($message=$session['error']??null){echo"<div class='error'>$message</div>";}if($message=$session['success']??null){echo"<div class='success'>$message</div>";}
Nonce System
Nonces provide CSRF protection and replay attack detection.
Terminology
Nonce: Number used ONCE - cryptographic token for action verification
Action: Type of operation being protected (e.g., 'form_submit', 'delete_user')
Protected Nonce: Can be verified multiple times with protection against rapid reuse
Complete Example: Form Protection
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
// 1. Display form with nonce
$nonce = $session->create_nonce('user_update', 36);
?>
<form method="POST" action="/update-profile">
(hl php)
// Automatically removes old Set-Cookie header before setting new one
// Prevents cookie header duplication
remove_cookie($name) → clears old headers
setcookie() → sets new header
$session->setcookie('token', 'value');
// Uses all configured defaults
==== Typical Secure Configuration ====
(hl php)
// Prevent XSS and CSRF
$session->cf_cookie_secure = true; // HTTPS only
$session->cf_cookie_httponly = true; // No JavaScript access
$session->cf_cookie_samesite = 'Strict'; // Strict CSRF protection
// Set scope
$session->cf_cookie_path = '/'; // Root path
$session->cf_cookie_domain = ''; // Current host only
// Session cookies (delete on browser close)
$session->cf_cookie_lifetime = ;
$session->cf_cookie_persistent = false;
----
=== Error Handling ===
==== Graceful Degradation ====
The Session class gracefully handles errors:
===== Headers Already Sent =====
(hl php)
if (headers_sent($file, $line)) {
trigger_error("id regeneration requested after headers flushed at $file:$line",
E_USER_WARNING);
return false;
}
**Impact:** Session ID cannot be regenerated, but session continues
===== Cookie Setting Failure =====
<!--markup:1:begin-->
php<!markup:1:end> PHP
<!--markup:2:end-->if(headers_sent($file,$line)){trigger_error("cannot place session cookie $name=$valuedue to $file:$line",E_USER_WARNING);return;}
Impact: Cookie not set, but session data remains accessible
Storage Errors
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
if ($this->store_read($this->id, true) !== '') {
// error! [comment indicates error, but continues]
}
**Impact:** Creates new session if storage returns error
==== Debug Logging ====
The Session class includes commented debug statements:
(hl php)
# Ut::dbg("regeneration failed by flush at $file:$line");
# Ut::dbg($destroy, $message);
# Ut::dbg("session setcookie $name failed by $file:$line");
To enable: Uncomment lines and ensure ##Ut::dbg()## function exists
==== Event Logging ====
Session events tracked in ##sticky__log##:
(hl php)
// Access session event history
if (isset($session->sticky__log)) {
foreach ($session->sticky__log as [$timestamp, $message]) {
echo "[$timestamp] $message\n";
}
}
**Logged Events:**
- Session regeneration (with reason)
- Limited to 15 most recent events (old entries archived as '...')
----
=== Implementation Guide ===
==== Creating a Concrete Session Class ====
You must implement the abstract storage methods. Choose your storage backend: files, database, cache, etc.
===== File-Based Storage =====
id VARCHAR(21) PRIMARY KEY,
data LONGTEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
SQL;
$this->pdo->exec($sql);
}
protected function store_open($name): void {
// Database already connected
}
protected function store_read($id, $lock = false): string|false {
$stmt = $this->pdo->prepare('SELECT data FROM sessions WHERE id = ?');
$stmt->execute([$id]);
if ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
return $result['data'];
}
if ($lock) {
// Create new session
$stmt = $this->pdo->prepare('INSERT INTO sessions (id, data) VALUES (?, ?)');
$stmt->execute([$id, '']);
return '';
}
return false;
}
protected function store_write($id, $data): void {
$stmt = $this->pdo->prepare(
'INSERT INTO sessions (id, data) VALUES (?, ?)
ON DUPLICATE KEY UPDATE data = VALUES(data)'
);
$stmt->execute([$id, $data]);
}
protected function store_close(): void {
// Connection persists for application
}
protected function store_gc(): void {
$cutoff = time() - $this->cf_gc_maxlifetime;
$this->pdo->prepare('DELETE FROM sessions WHERE updated_at < FROM_UNIXTIME(?)')
->execute([$cutoff]);
}
}
===== Redis Storage =====
<!--markup:1:begin-->
php<!markup:1:end>
PHP
<!--markup:2:end--><?phpclassRedisSessionextendsSession{privateRedis$redis;privatestring$prefix='sess:';publicfunction__construct(Redis$redis){parent::__construct();$this->redis=$redis;}protectedfunctionstore_open($name):void{// Redis already connected}protectedfunctionstore_read($id,$lock=false):string|false{$data=$this->redis->get($this->prefix.$id);if($data!==false){return$data;}if($lock){// Create new session$this->redis->set($this->prefix.$id,'',['EX'=>$this->cf_gc_maxlifetime]);return'';}returnfalse;}protectedfunctionstore_write($id,$data):void{$this->redis->set($this->prefix.$id,$data,['EX'=>$this->cf_gc_maxlifetime]);}protectedfunctionstore_close():void{// Connection persists}protectedfunctionstore_gc():void{// Redis handles expiration automatically with TTL}}
Complete Integration Example
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
<?php
// Initialize session with configuration
$session = new FileSession();
// Test user agent validation
assert(isset($session->__user_agent));
// Test session ID format
assert(preg_match('/^[a-zA-Z-9]{21}$/', $session->id()));
// Test data persistence
$session['test_key'] = 'test_value';
$session->write_close();
// New request...
$session2 = new FileSession();
$session2->start('myapp');
assert($session2['test_key'] === 'test_value');
----
=== Security Checklist ===
Use this checklist when implementing sessions:
- [ ] Use HTTPS only in production
- [ ] Enable ##cf_cookie_secure##
- [ ] Enable ##cf_cookie_httponly##
- [ ] Set ##cf_cookie_samesite## to 'Strict' or 'Lax'
- [ ] Set appropriate ##cf_max_session## timeout
- [ ] Set appropriate ##cf_max_idle## timeout
- [ ] Enable ##cf_prevent_replay##
- [ ] Validate ##cf_ip## if possible
- [ ] Validate ##cf_tls## on HTTPS sites
- [ ] Use nonces for all state-changing forms
- [ ] Implement proper logout (call ##restart()##)
- [ ] Regenerate on privilege escalation (login)
- [ ] Monitor ##sticky__ip## for suspicious changes
- [ ] Review ##sticky__log## for attack patterns
- [ ] Implement garbage collection (##store_gc##)
- [ ] Hash session IDs before storing (see TODOs)
- [ ] Use secure random token generation
----
=== Common Patterns ===
==== Login Flow ====
<!--markup:1:begin-->
php<!markup:1:end>
PHP
<!--markup:2:end-->if($_POST['action']==='login'){$user=authenticate($_POST['username'],$_POST['password']);if($user){$session->regenerate_id(false,'login');// New ID after auth$session['user_id']=$user->id;$session['username']=$user->username;$session['roles']=$user->roles;header('Location: /dashboard');}else{$session->set_flash('error','Invalid credentials',1);header('Location: /login');}}
(hl php)
// After action
$session->set_flash('info', 'Profile updated successfully', 1);
// Display next page
if (isset($session['info'])) {
echo $session['info'];
}
----
=== Performance Considerations ===
<!--markup:1:begin-->==== Optimization Tips ====<!--markup:1:end-->
<!--markup:2:begin-->====Optimization Tips====<!--markup:2:end-->
1. **Minimize Session Writes:**
- Session data only written during ##write_close()## or regeneration
- No unnecessary serialization during reads
2. **Garbage Collection:**
- Probabilistic GC (based on ##cf_gc_probability##)
- Only runs on ~2% of requests by default
- Customize based on your session volume
3. **Nonce Cleanup:**
- Expired nonces automatically removed on verification
- Verified nonces removed from storage
- No manual cleanup needed
4. **Session ID Validation:**
- Regex-based validation is fast
- No database lookup needed
5. **Caching Strategy:**
- Cache expensive lookups between session operations
- Session data loaded once per request
==== Benchmarks ====
Typical performance on modern hardware:
- Session start: ~1-5ms (file) / ~2-1ms (database)
- Session write: <1ms (file) / 1-5ms (database)
- Nonce generation: <1ms
- Nonce verification: <1ms
----
=== Troubleshooting ===
==== Session Not Starting ====
<!--markup:1:begin-->
php<!markup:1:end>
PHP
<!--markup:2:end-->if(!$session->start('myapp')){// Check reasons:// 1. Headers already sent?// 2. Storage backend not initialized?// 3. Permissions issue on session directory?debug_backtrace();}
Cookie Not Setting
<!markup:1:begin>
php<!--markup:1:end-->
<!--markup:2:begin-->
(hl php)
// If setcookie() returns false:
// - Check if headers_sent()
// - Check if cookie name is RFC 2616 compliant
// - Check if cookie value is properly encoded
==== Session ID Not Regenerating ====
<!--markup:1:begin-->
php<!markup:1:end>
PHP
<!--markup:2:end-->// If regenerate_id() returns false:// - Headers might be sent// - $active might be false// - Already regenerated once in this requestif(!$session->regenerate_id()){error_log("Regeneration failed: headers sent or session inactive");}
Nonce Verification Failing
PHP
// If verify_nonce() returns false:// 1. Nonce might be expired// 2. Nonce might be for different action// 3. Nonce might have been used already// 4. Session might have been reset// Debug:var_dump($session->__nonces);// See stored nonces
Session Data Lost
PHP
// Possible causes:// 1. write_close() not called (usually automatic via shutdown)// 2. Storage backend failing silently// 3. File permissions issues// 4. Session timeout due to cf_max_idle// 5. IP/UA/TLS validation failure (check message())if($message=$session->message()){error_log("Session issue: $message");}
TODO Items (From Code <!markup:1:begin>Comments)
<!markup:1:end> <!markup:2:begin>Comments)===
The following improvements are planned:
Do not store session ID in filename or DB index - store hash instead
Improves security by not exposing IDs in storage layer
Would require hashing logic in store_* methods
Log of IP changes and other possible security alerts
Track sticky__ip changes more comprehensively
Create security audit trail
Allocate internal unique session which lives through lifetime of uber-session
Multi-session management (parent/child sessions)
Useful for complex user flows
Do not delete old sessions, but use them as hijack pointers
Maintain session history for analysis
Detect potential session hijacking patterns
Implement session relationship tracking
All SIDs used later than 5secs of regenerations is hijacks
Detect and block delayed session ID usage
Current implementation allows 5-second window
Could be more granular
References
Security Standards
RFC 2616: HTTP/1.1 (Cookie syntax)
RFC 6265: HTTP State Management Mechanism
RFC 6234: US Secure Hash and Message Authentication Code Algorithms