Skip to content

Commit 2019ffe

Browse files
committed
feat(cli): add --body-field option to har:sanitize command
Add support for redacting body fields via the --body-field option which can be specified multiple times. Usage: har:sanitize input.har output.har --body-field password --body-field api_key
1 parent 3560b7a commit 2019ffe

2 files changed

Lines changed: 252 additions & 1 deletion

File tree

src/Command/SanitizeCommand.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ protected function configure(): void
2626
->addArgument('har', InputArgument::REQUIRED, 'The source HAR file to sanitize.')
2727
->addArgument('output', InputArgument::OPTIONAL, 'The output file path. Defaults to stdout.')
2828
->addOption('header', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Header name to redact (can be specified multiple times).')
29-
->addOption('query-param', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Query parameter name to redact (can be specified multiple times).');
29+
->addOption('query-param', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Query parameter name to redact (can be specified multiple times).')
30+
->addOption('body-field', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Body field name to redact (can be specified multiple times).');
3031
}
3132

3233
protected function execute(InputInterface $input, OutputInterface $output): int
@@ -68,6 +69,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
6869
$sanitizer->redactQueryParams($queryParams);
6970
}
7071

72+
$bodyFields = $input->getOption('body-field');
73+
if (!empty($bodyFields)) {
74+
$sanitizer->redactBodyFields($bodyFields);
75+
}
76+
7177
$sanitized = $sanitizer->sanitize($har);
7278
$result = $serializer->serializeHar($sanitized);
7379

tests/src/Functional/SanitizeCommandTest.php

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
namespace Deviantintegral\Har\Tests\Functional;
66

77
use Deviantintegral\Har\Command\SanitizeCommand;
8+
use Deviantintegral\Har\Content;
89
use Deviantintegral\Har\Creator;
910
use Deviantintegral\Har\Entry;
1011
use Deviantintegral\Har\Har;
1112
use Deviantintegral\Har\Log;
1213
use Deviantintegral\Har\Params;
14+
use Deviantintegral\Har\PostData;
1315
use Deviantintegral\Har\Request;
1416
use Deviantintegral\Har\Response;
1517
use Deviantintegral\Har\Serializer;
@@ -184,6 +186,8 @@ public function testCommandConfiguration(): void
184186
$this->assertTrue($definition->getOption('header')->isArray());
185187
$this->assertTrue($definition->hasOption('query-param'));
186188
$this->assertTrue($definition->getOption('query-param')->isArray());
189+
$this->assertTrue($definition->hasOption('body-field'));
190+
$this->assertTrue($definition->getOption('body-field')->isArray());
187191
}
188192

189193
public function testSanitizeWithNoOptions(): void
@@ -304,6 +308,103 @@ public function testSanitizeHeadersAndQueryParamsTogether(): void
304308
}
305309
}
306310

311+
public function testSanitizeBodyFields(): void
312+
{
313+
$harFile = $this->createHarFileWithPostData([
314+
'username' => 'john',
315+
'password' => 'secret123',
316+
'remember_me' => 'true',
317+
]);
318+
$outputFile = $this->tempDir.'/sanitized.har';
319+
320+
$this->commandTester->execute([
321+
'har' => $harFile,
322+
'output' => $outputFile,
323+
'--body-field' => ['password'],
324+
]);
325+
326+
$this->assertSame(Command::SUCCESS, $this->commandTester->getStatusCode());
327+
328+
$serializer = new Serializer();
329+
$sanitized = $serializer->deserializeHar(file_get_contents($outputFile));
330+
331+
$postData = $sanitized->getLog()->getEntries()[0]->getRequest()->getPostData();
332+
$params = $postData->getParams();
333+
$paramMap = $this->paramsToMap($params);
334+
335+
$this->assertEquals('john', $paramMap['username']);
336+
$this->assertEquals('[REDACTED]', $paramMap['password']);
337+
$this->assertEquals('true', $paramMap['remember_me']);
338+
}
339+
340+
public function testSanitizeMultipleBodyFields(): void
341+
{
342+
$harFile = $this->createHarFileWithPostData([
343+
'username' => 'john',
344+
'password' => 'secret123',
345+
'api_key' => 'key-456',
346+
'public_field' => 'visible',
347+
]);
348+
$outputFile = $this->tempDir.'/sanitized.har';
349+
350+
$this->commandTester->execute([
351+
'har' => $harFile,
352+
'output' => $outputFile,
353+
'--body-field' => ['password', 'api_key'],
354+
]);
355+
356+
$this->assertSame(Command::SUCCESS, $this->commandTester->getStatusCode());
357+
358+
$serializer = new Serializer();
359+
$sanitized = $serializer->deserializeHar(file_get_contents($outputFile));
360+
361+
$postData = $sanitized->getLog()->getEntries()[0]->getRequest()->getPostData();
362+
$params = $postData->getParams();
363+
$paramMap = $this->paramsToMap($params);
364+
365+
$this->assertEquals('john', $paramMap['username']);
366+
$this->assertEquals('[REDACTED]', $paramMap['password']);
367+
$this->assertEquals('[REDACTED]', $paramMap['api_key']);
368+
$this->assertEquals('visible', $paramMap['public_field']);
369+
}
370+
371+
public function testSanitizeAllOptionsTogether(): void
372+
{
373+
$harFile = $this->createHarFileWithAllData(
374+
['token' => 'secret-token', 'page' => '1'],
375+
['password' => 'secret123', 'username' => 'john']
376+
);
377+
$outputFile = $this->tempDir.'/sanitized.har';
378+
379+
$this->commandTester->execute([
380+
'har' => $harFile,
381+
'output' => $outputFile,
382+
'--header' => ['Host'],
383+
'--query-param' => ['token'],
384+
'--body-field' => ['password'],
385+
]);
386+
387+
$this->assertSame(Command::SUCCESS, $this->commandTester->getStatusCode());
388+
389+
$serializer = new Serializer();
390+
$sanitized = $serializer->deserializeHar(file_get_contents($outputFile));
391+
392+
$entry = $sanitized->getLog()->getEntries()[0];
393+
394+
// Check query params
395+
$queryParams = $entry->getRequest()->getQueryString();
396+
$queryMap = $this->paramsToMap($queryParams);
397+
$this->assertEquals('[REDACTED]', $queryMap['token']);
398+
$this->assertEquals('1', $queryMap['page']);
399+
400+
// Check body fields
401+
$postData = $entry->getRequest()->getPostData();
402+
$bodyParams = $postData->getParams();
403+
$bodyMap = $this->paramsToMap($bodyParams);
404+
$this->assertEquals('[REDACTED]', $bodyMap['password']);
405+
$this->assertEquals('john', $bodyMap['username']);
406+
}
407+
307408
/**
308409
* @param \Deviantintegral\Har\Header[] $headers
309410
*
@@ -355,12 +456,156 @@ private function createHarFileWithQueryParams(array $queryParams): string
355456
->setHeadersSize(-1)
356457
->setBodySize(0);
357458

459+
$content = (new Content())
460+
->setSize(0)
461+
->setMimeType('text/html');
462+
463+
$response = (new Response())
464+
->setStatus(200)
465+
->setStatusText('OK')
466+
->setHttpVersion('HTTP/1.1')
467+
->setHeaders([])
468+
->setCookies([])
469+
->setContent($content)
470+
->setHeadersSize(-1)
471+
->setBodySize(0);
472+
473+
$entry = (new Entry())
474+
->setStartedDateTime(new \DateTime())
475+
->setTime(100)
476+
->setRequest($request)
477+
->setResponse($response);
478+
479+
$creator = (new Creator())
480+
->setName('Test')
481+
->setVersion('1.0');
482+
483+
$log = (new Log())
484+
->setVersion('1.2')
485+
->setCreator($creator)
486+
->setEntries([$entry]);
487+
488+
$har = (new Har())->setLog($log);
489+
490+
$serializer = new Serializer();
491+
$harContent = $serializer->serializeHar($har);
492+
493+
$filePath = $this->tempDir.'/input-'.uniqid().'.har';
494+
file_put_contents($filePath, $harContent);
495+
496+
return $filePath;
497+
}
498+
499+
/**
500+
* @param array<string, string> $postParams
501+
*/
502+
private function createHarFileWithPostData(array $postParams): string
503+
{
504+
$paramObjects = [];
505+
foreach ($postParams as $name => $value) {
506+
$param = (new Params())->setName($name)->setValue($value);
507+
$paramObjects[] = $param;
508+
}
509+
510+
$postData = (new PostData())
511+
->setMimeType('application/x-www-form-urlencoded')
512+
->setParams($paramObjects);
513+
514+
$request = (new Request())
515+
->setMethod('POST')
516+
->setUrl(new Uri('https://example.com/api'))
517+
->setHttpVersion('HTTP/1.1')
518+
->setHeaders([])
519+
->setCookies([])
520+
->setQueryString([])
521+
->setPostData($postData)
522+
->setHeadersSize(-1)
523+
->setBodySize(0);
524+
525+
$content = (new Content())
526+
->setSize(0)
527+
->setMimeType('text/html');
528+
529+
$response = (new Response())
530+
->setStatus(200)
531+
->setStatusText('OK')
532+
->setHttpVersion('HTTP/1.1')
533+
->setHeaders([])
534+
->setCookies([])
535+
->setContent($content)
536+
->setHeadersSize(-1)
537+
->setBodySize(0);
538+
539+
$entry = (new Entry())
540+
->setStartedDateTime(new \DateTime())
541+
->setTime(100)
542+
->setRequest($request)
543+
->setResponse($response);
544+
545+
$creator = (new Creator())
546+
->setName('Test')
547+
->setVersion('1.0');
548+
549+
$log = (new Log())
550+
->setVersion('1.2')
551+
->setCreator($creator)
552+
->setEntries([$entry]);
553+
554+
$har = (new Har())->setLog($log);
555+
556+
$serializer = new Serializer();
557+
$harContent = $serializer->serializeHar($har);
558+
559+
$filePath = $this->tempDir.'/input-'.uniqid().'.har';
560+
file_put_contents($filePath, $harContent);
561+
562+
return $filePath;
563+
}
564+
565+
/**
566+
* @param array<string, string> $queryParams
567+
* @param array<string, string> $postParams
568+
*/
569+
private function createHarFileWithAllData(array $queryParams, array $postParams): string
570+
{
571+
$queryParamObjects = [];
572+
foreach ($queryParams as $name => $value) {
573+
$param = (new Params())->setName($name)->setValue($value);
574+
$queryParamObjects[] = $param;
575+
}
576+
577+
$postParamObjects = [];
578+
foreach ($postParams as $name => $value) {
579+
$param = (new Params())->setName($name)->setValue($value);
580+
$postParamObjects[] = $param;
581+
}
582+
583+
$postData = (new PostData())
584+
->setMimeType('application/x-www-form-urlencoded')
585+
->setParams($postParamObjects);
586+
587+
$request = (new Request())
588+
->setMethod('POST')
589+
->setUrl(new Uri('https://example.com/api'))
590+
->setHttpVersion('HTTP/1.1')
591+
->setHeaders([])
592+
->setCookies([])
593+
->setQueryString($queryParamObjects)
594+
->setPostData($postData)
595+
->setHeadersSize(-1)
596+
->setBodySize(0);
597+
598+
$content = (new Content())
599+
->setSize(0)
600+
->setMimeType('text/html');
601+
358602
$response = (new Response())
359603
->setStatus(200)
360604
->setStatusText('OK')
361605
->setHttpVersion('HTTP/1.1')
362606
->setHeaders([])
363607
->setCookies([])
608+
->setContent($content)
364609
->setHeadersSize(-1)
365610
->setBodySize(0);
366611

0 commit comments

Comments
 (0)