Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
workflow_run:
workflows: ["CI", "Skill Release"]
types: [completed]
# Note: No branch restriction - must trigger on both main branch CI runs AND tag-based Skill Releases
workflow_dispatch:

permissions:
Expand All @@ -19,8 +18,25 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
# Only run if workflow_dispatch OR the triggering workflow succeeded
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
# Production build only: manual dispatch or trusted workflow_run sources.
# PR validation runs in .github/workflows/pages-verify.yml.
if: |
github.event_name == 'workflow_dispatch' ||
(
github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success' &&
(
(
github.event.workflow_run.name == 'CI' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
) ||
(
github.event.workflow_run.name == 'Skill Release' &&
github.event.workflow_run.event != 'pull_request'
)
)
)
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand Down Expand Up @@ -401,7 +417,24 @@ jobs:
path: ./dist

deploy:
# Deploy after build succeeds (CI or Skill Release must pass first, or manual dispatch)
# Deploy after a production build succeeds.
if: |
github.event_name == 'workflow_dispatch' ||
(
github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success' &&
(
(
github.event.workflow_run.name == 'CI' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
) ||
(
github.event.workflow_run.name == 'Skill Release' &&
github.event.workflow_run.event != 'pull_request'
)
)
)
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
Expand Down
111 changes: 111 additions & 0 deletions .github/workflows/pages-verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Pages Verify

on:
pull_request:
branches: [main]

permissions:
contents: read

concurrency:
group: pages-verify-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
verify-pages-build:
name: Verify Pages Build (No Publish)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Verify signing key consistency (repo + docs)
run: ./scripts/ci/verify_signing_key_consistency.sh

- name: Prepare advisory artifacts for pre-deploy checks
run: |
set -euo pipefail
mkdir -p public/advisories
cp advisories/feed.json public/advisories/feed.json

- name: Generate advisory checksums manifest
run: |
set -euo pipefail

FEED_FILE="public/advisories/feed.json"
FEED_SHA=$(sha256sum "$FEED_FILE" | awk '{print $1}')
FEED_SIZE=$(stat -c%s "$FEED_FILE" 2>/dev/null || stat -f%z "$FEED_FILE")

jq -n \
--arg schema_version "1" \
--arg algorithm "sha256" \
--arg version "1.1.0" \
--arg generated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg repo "${{ github.repository }}" \
--arg sha "$FEED_SHA" \
--argjson size "$FEED_SIZE" \
'{
schema_version: $schema_version,
algorithm: $algorithm,
version: $version,
generated_at: $generated,
repository: $repo,
files: {
"advisories/feed.json": {
sha256: $sha,
size: $size,
path: "advisories/feed.json",
url: "https://clawsec.prompt.security/advisories/feed.json"
}
}
}' > public/checksums.json

- name: Generate ephemeral signing key for PR verification
id: test_key
run: |
set -euo pipefail
KEY_FILE=$(mktemp)
openssl genpkey -algorithm Ed25519 -out "$KEY_FILE"
{
echo "private_key<<EOF"
cat "$KEY_FILE"
echo "EOF"
} >> "$GITHUB_OUTPUT"
rm -f "$KEY_FILE"

- name: Sign advisory feed and verify
uses: ./.github/actions/sign-and-verify
with:
private_key: ${{ steps.test_key.outputs.private_key }}
input_file: public/advisories/feed.json
signature_file: public/advisories/feed.json.sig
public_key_output: public/signing-public.pem

- name: Sign checksums and verify
uses: ./.github/actions/sign-and-verify
with:
private_key: ${{ steps.test_key.outputs.private_key }}
input_file: public/checksums.json
signature_file: public/checksums.sig

- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build site
run: npm run build
env:
NODE_ENV: production

- name: Sanity-check generated artifacts
run: |
set -euo pipefail
test -f dist/index.html
test -f public/advisories/feed.json.sig
test -f public/checksums.sig
test -f public/signing-public.pem
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dist-ssr
# Derived public assets (copied during build)
public/advisories
public/skills
public/wiki/

# Python bytecode
__pycache__/
Expand Down
4 changes: 3 additions & 1 deletion App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FeedSetup } from './pages/FeedSetup';
import { SkillsCatalog } from './pages/SkillsCatalog';
import { SkillDetail } from './pages/SkillDetail';
import { AdvisoryDetail } from './pages/AdvisoryDetail';
import { WikiBrowser } from './pages/WikiBrowser';

const App: React.FC = () => {
return (
Expand All @@ -17,10 +18,11 @@ const App: React.FC = () => {
<Route path="/skills/:skillId" element={<SkillDetail />} />
<Route path="/feed" element={<FeedSetup />} />
<Route path="/feed/:advisoryId" element={<AdvisoryDetail />} />
<Route path="/wiki/*" element={<WikiBrowser />} />
</Routes>
</Layout>
</Router>
);
};

export default App;
export default App;
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,8 @@ Each skill release includes:
### Signing Operations Documentation

For feed/release signing rollout and operations guidance:
- [`docs/SECURITY-SIGNING.md`](docs/SECURITY-SIGNING.md) - key generation, GitHub secrets, rotation/revocation, incident response
- [`docs/MIGRATION-SIGNED-FEED.md`](docs/MIGRATION-SIGNED-FEED.md) - phased migration from unsigned feed, enforcement gates, rollback plan
- [`wiki/security-signing-runbook.md`](wiki/security-signing-runbook.md) - key generation, GitHub secrets, rotation/revocation, incident response
- [`wiki/migration-signed-feed.md`](wiki/migration-signed-feed.md) - phased migration from unsigned feed, enforcement gates, rollback plan

---

Expand Down Expand Up @@ -375,6 +375,9 @@ npm run dev

# Populate advisory feed with real NVD CVE data
./scripts/populate-local-feed.sh --days 120

# Generate wiki llms exports from wiki/ (for local preview)
./scripts/populate-local-wiki.sh
```

### Build
Expand All @@ -395,6 +398,7 @@ npm run build
├── scripts/
│ ├── populate-local-feed.sh # Local CVE feed populator
│ ├── populate-local-skills.sh # Local skills catalog populator
│ ├── populate-local-wiki.sh # Local wiki llms export populator
│ └── release-skill.sh # Manual skill release helper
├── skills/
│ ├── clawsec-suite/ # 📦 Suite installer (skill-of-skills)
Expand Down
3 changes: 2 additions & 1 deletion components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { NavLink } from 'react-router-dom';
import { Menu, X, Terminal, Layers, Rss, Home, Github } from 'lucide-react';
import { Menu, X, Terminal, Layers, Rss, Home, Github, BookOpenText } from 'lucide-react';

export const Header: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
Expand All @@ -9,6 +9,7 @@ export const Header: React.FC = () => {
{ label: 'Home', path: '/', icon: Home },
{ label: 'Skills', path: '/skills', icon: Layers },
{ label: 'Security Feed', path: '/feed', icon: Rss },
{ label: 'Wiki', path: '/wiki', icon: BookOpenText },
];

const baseLink =
Expand Down
Loading