-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRequestIsolation.php
More file actions
228 lines (198 loc) · 5.96 KB
/
Copy pathRequestIsolation.php
File metadata and controls
228 lines (198 loc) · 5.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
<?php
declare(strict_types=1);
namespace PivotPHP\ReactPHP\Security;
use Psr\Http\Message\ServerRequestInterface;
use PivotPHP\ReactPHP\Security\RequestIsolationInterface;
/**
* Request Isolation Manager
*
* Ensures each request has its own isolated context
* and prevents data leakage between requests
*/
final class RequestIsolation implements RequestIsolationInterface
{
private array $globalBackup = [];
private array $staticBackup = [];
private array $requestContexts = [];
/**
* Create isolated context for a request
*/
public function createContext(ServerRequestInterface $request): string
{
$contextId = $this->generateContextId($request);
// Initialize clean context
$this->requestContexts[$contextId] = [
'started_at' => microtime(true),
'globals_backup' => $this->backupGlobals(),
'static_properties' => [],
'memory_start' => memory_get_usage(true),
];
// Reset dangerous globals
$this->resetGlobals();
return $contextId;
}
/**
* Get context information
*/
public function getContextInfo(string $contextId): ?array
{
return $this->requestContexts[$contextId] ?? null;
}
/**
* Check if context exists
*/
public function hasContext(string $contextId): bool
{
return isset($this->requestContexts[$contextId]);
}
public function getStaticBackup(): array
{
return $this->staticBackup;
}
public function getGlobalBackup(): array
{
return $this->globalBackup;
}
/**
* Restore original state after request
*/
public function destroyContext(string $contextId): void
{
if (!isset($this->requestContexts[$contextId])) {
return;
}
$context = $this->requestContexts[$contextId];
// Restore globals
$this->restoreGlobals($context['globals_backup']);
// Clear static properties that were modified
$this->clearStaticProperties($context['static_properties']);
// Force garbage collection
unset($this->requestContexts[$contextId]);
gc_collect_cycles();
}
/**
* Track static property modification
*/
public function trackStaticProperty(string $class, string $property, mixed $originalValue): void
{
$contextId = $this->getCurrentContextId();
if ($contextId !== null) {
$this->requestContexts[$contextId]['static_properties'][] = [
'class' => $class,
'property' => $property,
'original' => $originalValue,
];
}
}
/**
* Get current context ID from request attribute
*/
private function getCurrentContextId(): ?string
{
// This would be set in middleware
return $_SERVER['X_REQUEST_CONTEXT_ID'] ?? null;
}
/**
* Backup global state
*/
private function backupGlobals(): array
{
return [
'SERVER' => $_SERVER,
'GET' => $_GET,
'POST' => $_POST,
'FILES' => $_FILES,
'COOKIE' => $_COOKIE,
'SESSION' => $_SESSION,
'ENV' => $_ENV,
];
}
/**
* Reset globals to safe defaults
*/
private function resetGlobals(): void
{
$_GET = [];
$_POST = [];
$_FILES = [];
$_COOKIE = [];
$_SESSION = [];
$_REQUEST = [];
// Keep only safe SERVER variables
$safeServerVars = [
'PHP_SELF', 'SCRIPT_NAME', 'argv', 'argc', 'GATEWAY_INTERFACE',
'SERVER_ADDR', 'SERVER_NAME', 'SERVER_SOFTWARE',
'SERVER_PROTOCOL', 'REQUEST_TIME', 'REQUEST_TIME_FLOAT',
'DOCUMENT_ROOT', 'SCRIPT_FILENAME',
];
$preserved = [];
foreach ($safeServerVars as $var) {
if (isset($_SERVER[$var])) {
$preserved[$var] = $_SERVER[$var];
}
}
$_SERVER = $preserved;
}
/**
* Restore globals from backup
*/
private function restoreGlobals(array $backup): void
{
$_SERVER = $backup['SERVER'];
$_GET = $backup['GET'];
$_POST = $backup['POST'];
$_FILES = $backup['FILES'];
$_COOKIE = $backup['COOKIE'];
$_SESSION = $backup['SESSION'];
$_ENV = $backup['ENV'];
$_REQUEST = array_merge($_GET, $_POST, $_COOKIE);
}
/**
* Clear modified static properties
*/
private function clearStaticProperties(array $properties): void
{
foreach ($properties as $prop) {
try {
$reflection = new \ReflectionClass($prop['class']);
$property = $reflection->getProperty($prop['property']);
$property->setAccessible(true);
$property->setValue(null, $prop['original']);
} catch (\Throwable $e) {
// Log error but don't break the request
}
}
}
/**
* Generate unique context ID for request
*/
private function generateContextId(ServerRequestInterface $request): string
{
return sprintf(
'%s_%s_%s',
uniqid('ctx_', true),
$request->getMethod(),
md5($request->getUri()->getPath())
);
}
/**
* Check if context is leaked (running too long)
*/
public function checkContextLeaks(): array
{
$leaks = [];
$now = microtime(true);
$maxDuration = 30.0; // 30 seconds max
foreach ($this->requestContexts as $contextId => $context) {
$duration = $now - $context['started_at'];
if ($duration > $maxDuration) {
$leaks[] = [
'context_id' => $contextId,
'duration' => $duration,
'memory_growth' => memory_get_usage(true) - $context['memory_start'],
];
}
}
return $leaks;
}
}