1 Commits

Author SHA1 Message Date
peptiderepo
e4c51ee89d v0.3.3 — P1 security: remove key-format prefix from API-key placeholder
class-prv-key-manager-renderer.php rendered placeholder="sk-or-..." on
the write-only password input, leaking the provider key format in page HTML.

This caused the smoke-suite write-only invariant assertion to fail (CI run
2026-06-17 job 81824705096: expect(html).not.toContain("sk-or-")). The
placeholder now reads "Paste provider API key" — neutral, no key-format
prefix, no provider information exposed in rendered HTML.

The value="" write-only invariant was already correct. The encrypted-at-rest
storage was already correct. This fixes the sole remaining output surface.

Full-codebase audit confirms no other path emits the decrypted key:
- No wp_localize_script calls anywhere in the plugin
- No REST endpoints registered
- No data attributes containing key fields
- All get_key() callers use the plaintext server-side only (HTTP auth header)
- error_log() in probe executor logs HTTP status only, never the key value

Agent-Session: prv-key-leak-fix-20260619
2026-06-19 09:42:53 +00:00
4 changed files with 49 additions and 5 deletions

View File

@@ -7,6 +7,44 @@ Versioning: [Semantic Versioning](https://semver.org/)
---
## [0.3.3] — 2026-06-19
P1 security fix: write-only key invariant — remove key-format prefix from placeholder.
### Fixed
- **[SECURITY] P1 — API-key input placeholder leaked provider prefix in rendered HTML.**
`class-prv-key-manager-renderer.php` rendered `placeholder="sk-or-…"` on the
password input. The placeholder contained the literal string `sk-or-` which (a)
exposes provider-format information to anyone who can view page source, and (b)
caused the `not.toContain('sk-or-')` smoke-suite assertion to fail (confirmed in
CI run on 2026-06-17, job 81824705096). The placeholder is now `Paste provider API
key` — neutral, no key-format prefix, no provider information. The `value=""` write-
only invariant was already correct; this fixes the remaining output surface.
- Added inline comment and docblock note: placeholder text must never contain a
key-format prefix — even a format hint in a placeholder violates the write-only
contract and trips smoke-suite security guards.
### Audit
Full-codebase audit performed (2026-06-19):
- `class-prv-settings-renderer.php` — no key emission; clean.
- `class-prv-key-manager-renderer.php` — placeholder was the sole leak; now fixed.
- `class-prv-model-manager-table.php` — no key fields; clean.
- `class-prv-settings-controller.php` — processes POST input; never reflects back.
- `class-prv-key-store.php` — `get_key()` used server-side only; never echoed.
- `class-prv-crypto-helper.php` — encrypt/decrypt helper; no HTML output.
- `class-prv-key-test-ajax.php` — AJAX; returns valid/invalid message, never the key.
- `class-prv-model-test-ajax.php` — AJAX; returns model health, never the key.
- `class-prv-gateway-client.php` — HTTP client; key only in Authorization header.
- `class-prv-probe-run-executor.php` — `error_log()` only logs HTTP status, not the key.
- `class-prv-capture-writer.php` — allowlisted fields; no key fields.
- `class-prv-call-drawer-renderer.php` / `class-prv-call-log-*` — no key fields.
- All providers — `get_key()` only flows to HTTP Authorization header.
- No `wp_localize_script` calls anywhere in the plugin.
- No REST endpoints registered; no REST response surfaces.
- No data attributes containing key fields.
---
## [0.3.2] — 2026-06-16
CI/CD adoption: PHPUnit test suite (WP stubs), estate reusable pipeline,

View File

@@ -12,7 +12,10 @@ declare(strict_types=1);
*
* Outputs only the key SOURCE (constant / admin / not set) and the
* last-run API status. The stored key value is NEVER included in any
* output: not in HTML, not in JS, not in data attributes.
* output: not in HTML, not in JS, not in data attributes. The password
* input placeholder must also never contain a key format prefix (e.g.
* 'sk-or-') — even a format hint in the placeholder leaks provider
* information and trips write-only security guards.
*
* Three states:
* - SOURCE_CONSTANT: constant defined → lock the input, show note.
@@ -120,10 +123,13 @@ class PRV_Key_Manager_Renderer {
echo '</label>';
// Input always renders empty — never outputs stored key.
// Placeholder must not include a key-format prefix (e.g. 'sk-or-');
// even a format hint leaks provider information and will trip
// write-only security assertions in the smoke suite.
echo '<input type="password" id="prv_api_key" name="prv_api_key" '
. 'class="prv-input" style="max-width:420px" '
. 'value="" autocomplete="new-password" '
. 'placeholder="' . esc_attr( $is_const ? __( 'Managed via wp-config — cannot override', 'pr-vision' ) : __( 'sk-or-…', 'pr-vision' ) ) . '"'
. 'placeholder="' . esc_attr( $is_const ? __( 'Managed via wp-config — cannot override', 'pr-vision' ) : __( 'Paste provider API key', 'pr-vision' ) ) . '"'
. ( $is_const ? ' disabled aria-disabled="true"' : '' ) . '>';
if ( $is_const ) {

View File

@@ -3,7 +3,7 @@
* Plugin Name: PR Vision
* Plugin URI: https://peptiderepo.com
* Description: Weekly server-side LLM probes for AI-visibility (GEO) tracking. Records whether peptiderepo.com is cited by LLMs across core peptides, stores time-series, and renders an admin dashboard + settings UI.
* Version: 0.3.2
* Version: 0.3.3
* Author: peptiderepo
* Author URI: https://peptiderepo.com
* License: GPL-2.0-or-later
@@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/* ── Constants ────────────────────────────────────────────────────────── */
define( 'PRV_VERSION', '0.3.2' );
define( 'PRV_VERSION', '0.3.3' );
define( 'PRV_PLUGIN_FILE', __FILE__ );
define( 'PRV_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'PRV_PLUGIN_URL', plugin_dir_url( __FILE__ ) );

View File

@@ -32,7 +32,7 @@ if ( ! defined( 'ARRAY_N' ) ) {
define( 'ARRAY_N', 'ARRAY_N' );
}
define( 'PRV_VERSION', '0.3.2' );
define( 'PRV_VERSION', '0.3.3' );
define( 'PRV_PLUGIN_FILE', __DIR__ . '/../pr-vision.php' );
define( 'PRV_PLUGIN_DIR', realpath( __DIR__ . '/..' ) . '/' );
define( 'PRV_PLUGIN_URL', 'http://example.test/wp-content/plugins/pr-vision/' );