diff --git a/.gitattributes b/.gitattributes index 4616110..71329db 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,8 @@ /.github/ export-ignore /tests/ export-ignore /.gitattributes export-ignore +/.gitguardian.yaml export-ignore +/.gitleaks.toml export-ignore /.gitignore export-ignore /.editorconfig export-ignore /BUILD-LOG.md export-ignore diff --git a/.gitguardian.yaml b/.gitguardian.yaml new file mode 100644 index 0000000..00f2544 --- /dev/null +++ b/.gitguardian.yaml @@ -0,0 +1,13 @@ +# Secret-scan policy. Written by agent-kit's secret_scan.py; extend it by hand, narrowly. +# +# ggshield reads this file (pre-commit, CI, local scans). The GitGuardian GitHub App, which posts +# the "GitGuardian Security Checks" run on pull requests, does not: its exclusions live in the +# GitGuardian dashboard (Settings -> Secrets detection -> Exclusion rules). +# +# Only named fixture and example-env paths are ignored. Never src/, never a real .env, never a +# detector. secret_scan.py still searches these paths for live-format keys. If a finding is real, +# rotate it first and then remove it; never add it here. +version: 2 +secret: + ignored_paths: + - "tests/Fixtures/**/*" diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..e08482f --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,12 @@ +# gitleaks policy. Written by agent-kit's secret_scan.py; extend it by hand, narrowly. +# Only named fixture and example-env paths are allowlisted; secret_scan.py still +# searches them for live-format keys. A realistic fake elsewhere carries an inline +# `gitleaks:allow` comment instead. +[extend] +useDefault = true + +[[allowlists]] +description = "Named fixture and example-env paths (agent-kit secret_scan.py)" +paths = [ + '''^tests/Fixtures/(?:.*/)?[^/]*$''', +] diff --git a/tests/Feature/ApiReadTest.php b/tests/Feature/ApiReadTest.php index fdc587e..47852b3 100644 --- a/tests/Feature/ApiReadTest.php +++ b/tests/Feature/ApiReadTest.php @@ -14,13 +14,13 @@ }); it('lists keys and masks secrets when enabled', function () { - $this->bindEnv("APP_NAME=Acme\nDB_PASSWORD=topsecret123\n"); + $this->bindEnv("APP_NAME=Acme\nDB_PASSWORD=redaction-canary-not-a-secret\n"); config(['laranail.env-kit-webui.enabled' => true]); $response = $this->getJson('api/v1/env-kit/keys')->assertOk(); $response->assertJsonFragment(['key' => 'APP_NAME', 'value' => 'Acme', 'secret' => false]); - expect($response->getContent())->not->toContain('topsecret123'); + expect($response->getContent())->not->toContain('redaction-canary-not-a-secret'); }); it('shows a single key', function () { @@ -41,10 +41,10 @@ }); it('reveals secrets when configured', function () { - $this->bindEnv("DB_PASSWORD=topsecret123\n"); + $this->bindEnv("DB_PASSWORD=test-secret-not-real\n"); config(['laranail.env-kit-webui.enabled' => true, 'laranail.env-kit-webui.reveal_secrets' => true]); $this->getJson('api/v1/env-kit/keys/DB_PASSWORD') ->assertOk() - ->assertJsonPath('data.value', 'topsecret123'); + ->assertJsonPath('data.value', 'test-secret-not-real'); }); diff --git a/tests/Feature/LivewireTest.php b/tests/Feature/LivewireTest.php index 00ae886..1d17cb8 100644 --- a/tests/Feature/LivewireTest.php +++ b/tests/Feature/LivewireTest.php @@ -13,11 +13,11 @@ beforeEach(fn () => config(['laranail.env-kit-webui.enabled' => true])); it('lists variables and masks secrets in the reactive panel', function () { - $this->bindEnv("APP_NAME=Acme\nDB_PASSWORD=topsecret123\n"); + $this->bindEnv("APP_NAME=Acme\nDB_PASSWORD=redaction-canary-not-a-secret\n"); Livewire::test(EnvKitPanelComponent::class) ->assertSee('APP_NAME') - ->assertDontSee('topsecret123'); + ->assertDontSee('redaction-canary-not-a-secret'); }); it('edits a value reactively through the engine', function () { @@ -62,7 +62,7 @@ }); it('surfaces a guard failure inline instead of crashing', function () { - $this->bindEnv("DB_PASSWORD=secret\n", ['laranail.env-kit.auto_backup' => false]); + $this->bindEnv("DB_PASSWORD=test-secret-not-real\n", ['laranail.env-kit.auto_backup' => false]); Livewire::test(EnvKitPanelComponent::class) ->call('startEditing', 'DB_PASSWORD') // protected key @@ -71,5 +71,5 @@ ->assertHasErrors('draft') ->assertSet('editingKey', 'DB_PASSWORD'); // stays in edit mode, not committed - expect(EnvKit::get('DB_PASSWORD'))->toBe('secret'); // unchanged + expect(EnvKit::get('DB_PASSWORD'))->toBe('test-secret-not-real'); // unchanged }); diff --git a/tests/Feature/PanelTest.php b/tests/Feature/PanelTest.php index 2876138..d1422eb 100644 --- a/tests/Feature/PanelTest.php +++ b/tests/Feature/PanelTest.php @@ -16,14 +16,14 @@ }); it('renders the panel HTML (default unstyled theme), masking secrets', function () { - $this->bindEnv("APP_NAME=Acme\nDB_PASSWORD=topsecret123\n"); + $this->bindEnv("APP_NAME=Acme\nDB_PASSWORD=redaction-canary-not-a-secret\n"); config(['laranail.env-kit-webui.enabled' => true]); $this->get('env-kit') ->assertOk() ->assertSee('unstyled') ->assertSee('APP_NAME') - ->assertDontSee('topsecret123'); + ->assertDontSee('redaction-canary-not-a-secret'); }); it('renders the configured theme with its classes', function () { diff --git a/tests/Feature/PolicyTest.php b/tests/Feature/PolicyTest.php index b5e77cb..1ca27bc 100644 --- a/tests/Feature/PolicyTest.php +++ b/tests/Feature/PolicyTest.php @@ -9,12 +9,12 @@ beforeEach(fn () => config(['laranail.env-kit-webui.enabled' => true])); it('honours a custom hidden_keys pattern in the API (masking)', function () { - $this->bindEnv("APP_NAME=Acme\nWIDGET_API=topsecret123\n"); + $this->bindEnv("APP_NAME=Acme\nWIDGET_API=redaction-canary-not-a-secret\n"); config(['laranail.env-kit.hidden_keys' => ['WIDGET_*']]); $response = $this->getJson('api/v1/env-kit/keys')->assertOk(); - expect($response->getContent())->not->toContain('topsecret123'); + expect($response->getContent())->not->toContain('redaction-canary-not-a-secret'); $response->assertJsonFragment(['key' => 'WIDGET_API', 'secret' => true]) ->assertJsonFragment(['key' => 'APP_NAME', 'value' => 'Acme', 'secret' => false]); });