Skip to content
Open
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
2 changes: 2 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ drove it on.
- [ ] Edited `lib/shell/` rather than a stamped region, ran `node plugins/vstack/lib/build-shell.mjs stamp`, and committed both.
- [ ] Added or renamed a plugin, and updated `.claude-plugin/marketplace.json` in the same commit.
- [ ] Renamed a tool, and added its former directory name to the `LEGACY` map in `lib/workdir.mjs`.
- [ ] Every security scan passes, and no finding was silenced instead of fixed.
- [ ] Added a step that uses an action, and pinned it by commit SHA with the version in a trailing comment.
16 changes: 16 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: 2

# Actions are pinned by commit SHA, which is immutable and therefore never
# picks up an upstream fix on its own. Dependabot moves the pin and rewrites
# the version comment beside it.
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
# A compromised release is usually pulled within days of publication, so
# wait before moving a pin onto it.
cooldown:
default-days: 7
commit-message:
prefix: ci
22 changes: 15 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
matrix:
node: ['18', '22']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node }}
- name: Review lifecycle
Expand All @@ -33,8 +35,10 @@
name: Stamped shell is current
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
# Fails when a page's stamped region has drifted from lib/shell/.
Expand All @@ -46,12 +50,16 @@
name: Manifests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
# Unpinned on purpose: this job has to run the validator a marketplace
# reviewer would run today, and there is no lockfile to pin it against.
- name: Install Claude Code
run: npm install --global @anthropic-ai/claude-code
run: npm install --global @anthropic-ai/claude-code # zizmor: ignore[adhoc-packages]

Check warning on line 62 in .github/workflows/ci.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--ignore-scripts" allows lifecycle scripts to run during package installation.

See more on https://sonarcloud.io/project/issues?id=Cavalry-Collective_visual-stack&issues=AZ_SOtnGxH35Z5Uc_ug2&open=AZ_SOtnGxH35Z5Uc_ug2&pullRequest=5

Check warning on line 62 in .github/workflows/ci.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=Cavalry-Collective_visual-stack&issues=AZ_SOtnGxH35Z5Uc_ug3&open=AZ_SOtnGxH35Z5Uc_ug3&pullRequest=5
# The community-marketplace review pipeline runs this same check on every
# submission, so a warning here is a warning a reviewer would see.
- name: Validate the marketplace
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ jobs:
name: Tag matches the manifest version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# An explicit version in plugin.json is what Claude Code compares against
# to decide an update exists. Tagging a release without bumping it leaves
# every installed copy believing it is already current.
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Scorecard

# OpenSSF Scorecard rates the repository's supply-chain posture: branch
# protection, pinned actions, token permissions, release signing. It reads the
# repository rather than the code, so it runs on main and on a schedule, not on
# a pull request.
on:
branch_protection_rule:
push:
branches: [main]
schedule:
- cron: '41 5 * * 1'

permissions:
contents: read

jobs:
analysis:
name: OpenSSF Scorecard
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
# Publishing the result is what lets the README badge read a score.
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Run Scorecard
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- name: Upload the result
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
sarif_file: results.sarif
123 changes: 123 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: Security

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# A weekly run finds a rule or advisory published after the last commit.
- cron: '23 5 * * 1'

permissions:
contents: read

Check warning on line 13 in .github/workflows/security.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move this read permission from workflow level to job level.

See more on https://sonarcloud.io/project/issues?id=Cavalry-Collective_visual-stack&issues=AZ_SOtpyxH35Z5Uc_ug4&open=AZ_SOtpyxH35Z5Uc_ug4&pullRequest=5

jobs:
sast:
name: SAST (CodeQL)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Initialise CodeQL
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
# HTML is scanned too: every page carries inline script.
languages: javascript-typescript
queries: security-and-quality
- name: Analyse
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6

secrets:
name: Secret scan (Gitleaks)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Anything ever committed is compromised, so the scan reads the whole
# history. A shallow clone shows only the tip.
fetch-depth: 0
persist-credentials: false
- name: Install Gitleaks
env:
# The GitHub Action wrapper needs a paid licence for organisation
# repositories. The CLI it wraps is MIT, so this installs that.
GITLEAKS_VERSION: 8.30.1
run: |
set -euo pipefail
cd "$RUNNER_TEMP"
release="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}"
curl -fsSL -O "${release}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"

Check warning on line 54 in .github/workflows/security.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Not enforcing HTTPS here might allow for redirections to insecure websites. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=Cavalry-Collective_visual-stack&issues=AZ_SOtpyxH35Z5Uc_ug5&open=AZ_SOtpyxH35Z5Uc_ug5&pullRequest=5
curl -fsSL -O "${release}/gitleaks_${GITLEAKS_VERSION}_checksums.txt"

Check warning on line 55 in .github/workflows/security.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Not enforcing HTTPS here might allow for redirections to insecure websites. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=Cavalry-Collective_visual-stack&issues=AZ_SOtpyxH35Z5Uc_ug6&open=AZ_SOtpyxH35Z5Uc_ug6&pullRequest=5
grep "gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
"gitleaks_${GITLEAKS_VERSION}_checksums.txt" | sha256sum -c -
tar -xzf "gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" gitleaks
- name: Scan every commit
run: |
"$RUNNER_TEMP/gitleaks" git --no-banner --redact --verbose

workflows:
name: Workflow audit (zizmor)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
actions: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# The workflows are the only thing here that runs with repository
# credentials, so they get their own static analysis.
- uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2
with:
version: 1.29.0

quality:
name: Code quality (SonarQube Cloud)
runs-on: ubuntu-latest
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Sonar attributes findings to the commit that introduced them.
fetch-depth: 0
persist-credentials: false
- name: Scan
if: env.SONAR_TOKEN != ''
uses: SonarSource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8.2.1
# A pull request from a fork cannot read the token, and neither can a
# clone that has not been bound to a SonarQube Cloud project yet.
- name: Report the skip
if: env.SONAR_TOKEN == ''
run: |
echo "SONAR_TOKEN is not set, so the code-quality scan did not run." \
>> "$GITHUB_STEP_SUMMARY"

gates:
name: Security gates
runs-on: ubuntu-latest
needs: [sast, secrets, workflows, quality]
if: always()
steps:
# `needs` alone does not gate: a skipped or cancelled scan satisfies it.
# This fails unless every scan actually reported success.
- name: Every scan succeeded
env:
RESULTS: ${{ toJSON(needs) }}
run: |
set -euo pipefail
failed=$(echo "$RESULTS" | jq -r '
to_entries[] | select(.value.result != "success") | "\(.key): \(.value.result)"
')
if [ -n "$failed" ]; then
echo "These scans did not succeed:"
echo "$failed"
exit 1
fi
echo "Every scan succeeded."
14 changes: 14 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ claude plugin validate ./plugins/vstack --strict # the plugin manifest

`.github/workflows/ci.yml` runs all of the above on every pull request.

The security scans run in `.github/workflows/security.yml`, and a merge is
blocked until every one of them passes. Two of them run locally:

```bash
gitleaks git --no-banner --redact --verbose # secrets, over the full history
uvx zizmor@1.29.0 .github/workflows/ # workflow audit
```

`SECURITY.md` owns what each gate enforces, the rule that a finding is fixed
rather than silenced, and the rule that every action is pinned by commit SHA
with its version in a trailing comment. Adding a step that uses an action means
resolving that SHA with
`gh api repos/<owner>/<action>/commits/<tag> --jq .sha`.

CI cannot install the plugin, so rehearse that locally before a release.
`CLAUDE_CONFIG_DIR` keeps it out of the real config: without it, a local-path
marketplace is written to user settings and shadows the published
Expand Down
17 changes: 17 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ claude plugin validate ./plugins/vstack --strict

Nothing runs end to end in CI, so a green build is not a tested skill.

## Passing the security gates

CI also runs the scans listed in [SECURITY.md](SECURITY.md), and a merge is blocked until all of them pass. Two of them you can run before you push:

```bash
gitleaks git --no-banner --redact --verbose # every commit, not the working tree
uvx zizmor@1.29.0 .github/workflows/ # only if you touched a workflow
```

CodeQL and SonarQube report in the pull request itself. Read the finding before assuming it is noise — the servers read from disk and the pages build DOM from stored comments, which is exactly where a real one would appear.

When you add a step that uses an action, pin it by commit SHA and put the version in a trailing comment, the way the existing steps do. Copy the SHA from the release you intend to use:

```bash
gh api repos/<owner>/<action>/commits/<tag> --jq .sha
```

CI also cannot install the plugin. Rehearse that locally before a release, with `CLAUDE_CONFIG_DIR` pointed at a throwaway directory so the local-path marketplace is not written to your real settings, where it would shadow the published `cavalry-collective`:

```bash
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

# Visual Stack

[![CI](https://github.com/Cavalry-Collective/visual-stack/actions/workflows/ci.yml/badge.svg)](https://github.com/Cavalry-Collective/visual-stack/actions/workflows/ci.yml)
[![Security](https://github.com/Cavalry-Collective/visual-stack/actions/workflows/security.yml/badge.svg)](https://github.com/Cavalry-Collective/visual-stack/actions/workflows/security.yml)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Cavalry-Collective/visual-stack/badge)](https://scorecard.dev/viewer/?uri=github.com/Cavalry-Collective/visual-stack)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

## Stop prompting. Start pointing.

Visual Stack adds a Figma-like feedback layer to AI coding agents.
Expand Down Expand Up @@ -93,6 +98,10 @@ Each workspace is linked to one agent session. The link holds while that session

![Your comments are submitted as one review round. The agent claims the round and reads its brief, asking for clarification when a comment is unclear. Comments sent while the round is in progress join it. Publishing is blocked until every comment has been applied, answered, or dismissed, and the published version appears in the same workspace.](docs/assets/review-lifecycle.svg)

## Security

Installing this plugin runs its code on your machine, so every change to `main` passes a set of scans before it lands: static analysis, a secret scan over the whole history, a workflow audit, and a code-quality gate. The badges above report the last run. [SECURITY.md](SECURITY.md) says what each gate enforces and how to report a vulnerability.

## Contribute

Visual Stack is open source and under active development. Expect rough edges, breaking changes, and occasional moments of character development.
Expand Down
18 changes: 18 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ In scope:

Out of scope: vulnerabilities in Claude Code itself — report those to Anthropic.

## What runs on every change

`.github/workflows/security.yml` runs these scans on every pull request, on every push to `main`, and weekly. The `Security gates` job fails unless all four report success, so a scan that is skipped or cancelled blocks the merge in the same way a failing one does.

| Scan | Tool | What it enforces |
|---|---|---|
| Static analysis | CodeQL, `security-and-quality` suite | Injection, path traversal, and unsafe DOM construction in the servers and the pages. Findings land in the repository's Security tab. |
| Secret scan | Gitleaks | No credential in any commit. It reads the full history, not the working tree, because anything ever committed is compromised. |
| Workflow audit | zizmor | The workflows themselves: token permissions, credential persistence, and untrusted input reaching a `run` block. |
| Code quality | SonarQube Cloud | Bugs, security hotspots, and maintainability on new code. Free for public projects. The scan reports a skip rather than failing where `SONAR_TOKEN` is unavailable, which is every pull request from a fork. |

`.github/workflows/scorecard.yml` runs OpenSSF Scorecard weekly and on `main`. It rates the repository rather than the code — branch protection, pinned actions, token permissions — and publishes the score the README badge reads.

Two rules keep those gates meaningful:

- Fix a finding rather than silencing it. A suppression carries a comment saying why it cannot be fixed, next to the line it applies to.
- Actions are pinned by commit SHA with the version in a trailing comment. A tag moves, and a moved tag runs code nobody reviewed. Dependabot proposes the bumps weekly.

## Supported versions

`main` only. There are no maintained release branches.
13 changes: 13 additions & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Read by the SonarQube Cloud scan in .github/workflows/security.yml.
# The project key and organisation are the ones SonarQube Cloud derives when a
# GitHub repository is imported.
sonar.projectKey=Cavalry-Collective_visual-stack
sonar.organization=cavalry-collective
sonar.projectName=Visual Stack

sonar.sources=.
sonar.exclusions=docs/assets/**,node_modules/**,**/.vstack/**

# The shared shell is stamped into every page by lib/build-shell.mjs, because a
# page has to work with no external requests. That duplication is the design.
sonar.cpd.exclusions=plugins/vstack/**/*.html
Loading