Compare commits
5 Commits
claude/fix
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99113790e6 | ||
|
|
71cabcd7fa | ||
|
|
0b1f95e6ed | ||
|
|
be9827ce13 | ||
|
|
27f2ebc495 |
121
.gitea/workflows/pipeline.yml
Normal file
121
.gitea/workflows/pipeline.yml
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
# GENERATED by peptide-e2e/tools/gitea-pipeline/render.py from repos.json.
|
||||||
|
# Do not edit by hand: change repos.json or the template and re-render.
|
||||||
|
name: CI / Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request: {}
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: pipeline-pr-vision-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-php:
|
||||||
|
name: PHP Lint (${{ matrix.php }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: git.protopeer.com/peptide-repo/ci-php:2026-10
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php: ["8.1", "8.2", "8.3"]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: https://git.protopeer.com/peptide-repo/peptide-e2e/.gitea/actions/ci-lint-php@main
|
||||||
|
with:
|
||||||
|
php: ${{ matrix.php }}
|
||||||
|
|
||||||
|
phpcs:
|
||||||
|
name: PHPCS (WordPress standards)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: git.protopeer.com/peptide-repo/ci-php:2026-10
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.head_ref || github.ref_name }}
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- uses: https://git.protopeer.com/peptide-repo/peptide-e2e/.gitea/actions/ci-phpcs@main
|
||||||
|
with:
|
||||||
|
autofix: 'true'
|
||||||
|
|
||||||
|
file-size:
|
||||||
|
name: 300-line rule (includes)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: git.protopeer.com/peptide-repo/ci-php:2026-10
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: https://git.protopeer.com/peptide-repo/peptide-e2e/.gitea/actions/ci-file-size@main
|
||||||
|
with:
|
||||||
|
paths: includes
|
||||||
|
|
||||||
|
phpunit:
|
||||||
|
name: PHPUnit - stubs (PHP ${{ matrix.php }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: git.protopeer.com/peptide-repo/ci-php:2026-10
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php: ["8.1", "8.2", "8.3"]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: https://git.protopeer.com/peptide-repo/peptide-e2e/.gitea/actions/ci-phpunit@main
|
||||||
|
with:
|
||||||
|
php: ${{ matrix.php }}
|
||||||
|
tests: stubs
|
||||||
|
|
||||||
|
deploy-staging:
|
||||||
|
name: Deploy to staging
|
||||||
|
needs: ["lint-php", "phpcs", "file-size", "phpunit"]
|
||||||
|
if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' }}
|
||||||
|
runs-on: peptide-vps
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: https://git.protopeer.com/peptide-repo/peptide-e2e/.gitea/actions/deploy@main
|
||||||
|
with:
|
||||||
|
stage: staging
|
||||||
|
target_path: wp-content/plugins/pr-vision
|
||||||
|
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
ssh_host: ${{ secrets.SSH_HOST }}
|
||||||
|
ssh_port: ${{ secrets.SSH_PORT }}
|
||||||
|
ssh_username: ${{ secrets.SSH_USERNAME }}
|
||||||
|
|
||||||
|
smoke:
|
||||||
|
name: Smoke tests (staging)
|
||||||
|
needs: deploy-staging
|
||||||
|
runs-on: peptide-vps
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- uses: https://git.protopeer.com/peptide-repo/peptide-e2e/.gitea/actions/smoke@main
|
||||||
|
with:
|
||||||
|
wp_admin_user: ${{ secrets.STAGING_WP_ADMIN_USER }}
|
||||||
|
wp_admin_password: ${{ secrets.STAGING_WP_ADMIN_PASSWORD }}
|
||||||
|
|
||||||
|
deploy-production:
|
||||||
|
name: Deploy to production
|
||||||
|
# Smoke is a gate: production runs only if staging deploy AND smoke succeeded.
|
||||||
|
# PROD_DEPLOY_ENABLED (org variable) is the cutover switch / kill switch.
|
||||||
|
needs: [deploy-staging, smoke]
|
||||||
|
if: ${{ vars.PROD_DEPLOY_ENABLED == 'true' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' }}
|
||||||
|
runs-on: peptide-vps
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: https://git.protopeer.com/peptide-repo/peptide-e2e/.gitea/actions/deploy@main
|
||||||
|
with:
|
||||||
|
stage: production
|
||||||
|
target_path: wp-content/plugins/pr-vision
|
||||||
|
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
ssh_host: ${{ secrets.SSH_HOST }}
|
||||||
|
ssh_port: ${{ secrets.SSH_PORT }}
|
||||||
|
ssh_username: ${{ secrets.SSH_USERNAME }}
|
||||||
|
|
||||||
|
audit-direct-push:
|
||||||
|
name: Audit - commit came from a PR
|
||||||
|
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: git.protopeer.com/peptide-repo/ci-php:2026-10
|
||||||
|
steps:
|
||||||
|
- uses: https://git.protopeer.com/peptide-repo/peptide-e2e/.gitea/actions/audit-direct-push@main
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
38
CHANGELOG.md
38
CHANGELOG.md
@@ -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
|
## [0.3.2] — 2026-06-16
|
||||||
|
|
||||||
CI/CD adoption: PHPUnit test suite (WP stubs), estate reusable pipeline,
|
CI/CD adoption: PHPUnit test suite (WP stubs), estate reusable pipeline,
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ declare(strict_types=1);
|
|||||||
*
|
*
|
||||||
* Outputs only the key SOURCE (constant / admin / not set) and the
|
* Outputs only the key SOURCE (constant / admin / not set) and the
|
||||||
* last-run API status. The stored key value is NEVER included in any
|
* 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:
|
* Three states:
|
||||||
* - SOURCE_CONSTANT: constant defined → lock the input, show note.
|
* - SOURCE_CONSTANT: constant defined → lock the input, show note.
|
||||||
@@ -120,10 +123,13 @@ class PRV_Key_Manager_Renderer {
|
|||||||
echo '</label>';
|
echo '</label>';
|
||||||
|
|
||||||
// Input always renders empty — never outputs stored key.
|
// 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" '
|
echo '<input type="password" id="prv_api_key" name="prv_api_key" '
|
||||||
. 'class="prv-input" style="max-width:420px" '
|
. 'class="prv-input" style="max-width:420px" '
|
||||||
. 'value="" autocomplete="new-password" '
|
. '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"' : '' ) . '>';
|
. ( $is_const ? ' disabled aria-disabled="true"' : '' ) . '>';
|
||||||
|
|
||||||
if ( $is_const ) {
|
if ( $is_const ) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: PR Vision
|
* Plugin Name: PR Vision
|
||||||
* Plugin URI: https://peptiderepo.com
|
* 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.
|
* 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: peptiderepo
|
||||||
* Author URI: https://peptiderepo.com
|
* Author URI: https://peptiderepo.com
|
||||||
* License: GPL-2.0-or-later
|
* License: GPL-2.0-or-later
|
||||||
@@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
|
|
||||||
/* ── Constants ────────────────────────────────────────────────────────── */
|
/* ── Constants ────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
define( 'PRV_VERSION', '0.3.2' );
|
define( 'PRV_VERSION', '0.3.3' );
|
||||||
define( 'PRV_PLUGIN_FILE', __FILE__ );
|
define( 'PRV_PLUGIN_FILE', __FILE__ );
|
||||||
define( 'PRV_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
define( 'PRV_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||||
define( 'PRV_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
define( 'PRV_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ if ( ! defined( 'ARRAY_N' ) ) {
|
|||||||
define( 'ARRAY_N', '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_FILE', __DIR__ . '/../pr-vision.php' );
|
||||||
define( 'PRV_PLUGIN_DIR', realpath( __DIR__ . '/..' ) . '/' );
|
define( 'PRV_PLUGIN_DIR', realpath( __DIR__ . '/..' ) . '/' );
|
||||||
define( 'PRV_PLUGIN_URL', 'http://example.test/wp-content/plugins/pr-vision/' );
|
define( 'PRV_PLUGIN_URL', 'http://example.test/wp-content/plugins/pr-vision/' );
|
||||||
|
|||||||
Reference in New Issue
Block a user