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
51 changes: 51 additions & 0 deletions docs/CONTRIBUTING_SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Security Checklist for Contributors — Closes #50

Review before pushing code. Supplements [CONTRIBUTING.md](./CONTRIBUTING.md).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align contributor documentation with the repository layout.

These documents use paths that can send contributors to missing or inactive locations.

  • docs/CONTRIBUTING_SECURITY.md#L3-L3: change ./CONTRIBUTING.md to ../CONTRIBUTING.md if the root-level file is intended.
  • docs/REPOSITORY_MAP.md#L17-L23: document the actual locations of route and validation code, including src/server.js and src/requestValidation.js.
  • docs/REPOSITORY_MAP.md#L39-L41: align the src/api/ guidance with the actual route-registration boundary.
📍 Affects 2 files
  • docs/CONTRIBUTING_SECURITY.md#L3-L3 (this comment)
  • docs/REPOSITORY_MAP.md#L17-L23
  • docs/REPOSITORY_MAP.md#L39-L41


## Before Opening a Pull Request

### Secrets & Credentials
- [ ] No API keys, tokens, or passwords in source code
- [ ] Environment variables used for all secrets (see `.env.example`)
- [ ] `.env` and `.env.local` are in `.gitignore`
- [ ] No hardcoded JWT secrets or signing keys

### Dependencies
- [ ] Run `npm audit` — no critical or high vulnerabilities
- [ ] New dependencies reviewed for maintenance status
- [ ] Lockfile (`package-lock.json`) updated
- [ ] No deprecated packages introduced

### Input Validation
- [ ] All user inputs validated server-side
- [ ] SQL/NoSQL injection protections in place
- [ ] File upload paths sanitized and size-limited
- [ ] XSS protections: output encoding, CSP headers

### Authentication & Authorization
- [ ] Endpoints gated with auth middleware
- [ ] Role-based access control enforced
- [ ] Session tokens use secure, httpOnly, SameSite flags
- [ ] Rate limiting on auth endpoints

### Data Protection
- [ ] Sensitive data encrypted at rest
- [ ] PII minimized
- [ ] Logging does not capture passwords, tokens, or PII
- [ ] CORS configured with specific origins (not `*`)

### API Security
- [ ] API responses don't leak stack traces
- [ ] Pagination limits on list endpoints
- [ ] Content-Type headers validated
- [ ] HTTPS enforced (HSTS header)

### Testing
- [ ] Security-focused test cases included
- [ ] Edge cases tested (empty input, max-length, special chars)
- [ ] Error paths tested

## CI/CD Pipeline
- [ ] `npm test` passes locally and in CI
- [ ] `npm run lint` passes with no errors
- [ ] Git hooks (husky) pass pre-commit checks
77 changes: 77 additions & 0 deletions docs/REPOSITORY_MAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Stellarmind Repository Map — Closes #48

A guided tour for new contributors.

## Top-Level Layout

```
stellarmind/
├── .github/ # CI workflows, issue templates
├── .husky/ # Git hooks (pre-commit checks)
├── docs/ # Project documentation
├── public/ # Static assets and entry HTML
│ ├── assets/ # CSS, JS, images
│ │ ├── css/ # Stylesheets
│ │ └── js/ # Client-side JavaScript
│ └── index.html # Main entry point
├── src/ # Application source code
│ ├── agents/ # AI agent orchestration modules
│ ├── api/ # REST API route handlers
│ ├── middleware/ # Express middleware (auth, validation)
│ ├── pricing/ # Pricing engine and calculators
│ ├── orchestrator/ # Multi-agent coordination logic
│ └── utils/ # Shared utilities and helpers
├── tests/ # Test suites
│ └── load/ # Load test scenarios (k6, Artillery)
├── package.json # Node.js dependencies and scripts
├── vercel.json # Vercel deployment configuration
├── CONTRIBUTING.md # Contribution guidelines
├── SECURITY.md # Security policy
└── README.md # Project overview
```

## Key Directories

### `src/agents/`
Each agent module handles a specific AI task. Agents communicate through the
orchestrator. See `src/orchestrator/` for the coordination layer.

### `src/api/`
Express route handlers. New endpoints should be registered here and follow
REST conventions. Each route file exports a router.

### `src/pricing/`
The pricing engine that calculates costs based on agent usage, token
consumption, and plan tiers. See `PRICING_INDEX.md` for business logic.

### `tests/`
- `*.test.js` — Unit and integration tests (Jest)
- `load/` — Performance and load tests (k6, Artillery)

## Quick Start (New Contributors)

1. **Read**: `CONTRIBUTING.md` and this repository map
2. **Setup**: `nvm use && npm install`
3. **Develop**: `npm run dev` starts the development server
4. **Test**: `npm test` runs the full test suite
5. **Lint**: `npm run lint` checks code style
6. **Submit**: Open a PR following the PR template

## Common Workflows

| Task | Command |
|------|---------|
| Start dev server | `npm run dev` |
| Run all tests | `npm test` |
| Run single test | `npx jest path/to/test` |
| Lint code | `npm run lint` |
| Build for production | `npm run build` |

## Architecture Overview

Stellarmind uses an **orchestrator pattern** where a central coordination
module dispatches tasks to specialized AI agents. Agents are stateless;
state is managed at the orchestrator level and persisted via the API layer.

For detailed architecture, see `FRONTEND_STRUCTURE.md` and the inline
documentation in `src/orchestrator/`.
61 changes: 61 additions & 0 deletions public/assets/css/accessibility.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* Accessibility Improvements — Closes #35 */
.skip-link {
position: absolute;
top: -40px;
left: 6px;
background: var(--bg-primary, #fff);
color: var(--text-primary, #000);
padding: 8px 16px;
z-index: 10000;
border-radius: 0 0 4px 4px;
text-decoration: none;
font-weight: 600;
transition: top 0.2s ease;
}
.skip-link:focus {
top: 0;
}
:focus-visible {
outline: 3px solid var(--focus-color, #4A90D9);
outline-offset: 2px;
border-radius: 2px;
}
:focus:not(:focus-visible) {
outline: none;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
@media (prefers-contrast: high) {
:root {
--border-color: #000;
--text-primary: #000;
--bg-primary: #fff;
}
button, .btn, input, select, textarea {
border: 2px solid #000;
}
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
button, .btn, [role="button"], a.nav-link, .clickable {
min-height: 44px;
min-width: 44px;
}
.text-muted {
color: #666 !important;
}
86 changes: 86 additions & 0 deletions public/assets/css/responsive.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* Mobile Responsiveness Improvements — Closes #34 */
/* Sidebar collapse on tablets */
@media (max-width: 1024px) {
.sidebar {
width: 60px;
overflow: hidden;
}
.sidebar:hover,
.sidebar:focus-within {
width: 240px;
}
.sidebar .nav-label {
display: none;
}
.sidebar:hover .nav-label,
.sidebar:focus-within .nav-label {
display: inline;
}
.main-content {
margin-left: 60px;
}
}

/* Full mobile collapse */
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
position: fixed;
z-index: 1000;
height: 100vh;
transition: transform 0.3s ease;
}
.sidebar.open {
transform: translateX(0);
width: 240px;
}
.sidebar.open .nav-label {
display: inline;
}
.main-content {
margin-left: 0;
padding: 1rem;
}
.mobile-menu-toggle {
display: block;
}
Comment on lines +25 to +46

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 --glob '*.js' --glob '*.html' \
  'mobile-menu-toggle|classList\.(toggle|add|remove)\([^)]*["'\'']open|aria-expanded' public

Repository: Flamki/stellarmind

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Tracked public JS/HTML/CSS files:\n'
git ls-files public | sed -n '1,200p'

printf '\nCSS file excerpt:\n'
sed -n '1,80p' public/assets/css/responsive.css 2>/dev/null || true

printf '\nSearch relevant tokens (all tracked text files):\n'
rg -n -C 3 'mobile-menu-toggle|sidebar\.open|classList\.(toggle|add|remove)\([^)]*["'\'']open|aria-expanded|aria-controls|onclick|addEventListener' public || true

Repository: Flamki/stellarmind

Length of output: 10929


🌐 Web query:

Flamki stellarmind public/assets/css/responsive.css mobile-menu-toggle sidebar.open

💡 Result:

The terms provided relate to a common pattern for implementing a responsive sidebar menu in web development, often found in projects like those managed under the Flamki organization on GitHub [1][2][3]. The file path public/assets/css/responsive.css typically contains the CSS rules for handling sidebar visibility on smaller screens, such as using media queries to toggle the display or transform properties (e.g., translateX) [2][4][3]. The terms mobile-menu-toggle and sidebar.open refer to the common JavaScript and CSS implementation where: 1. mobile-menu-toggle: Acts as a trigger element (often a hamburger icon or button) that captures click events [2][5][3]. 2. sidebar.open: A CSS class added or removed from the sidebar element via JavaScript to control its visibility (e.g., shifting it into or out of the viewport) [6][5][3]. While Flamki maintains several repositories (such as stellarmind, a marketplace for AI agents on Stellar) [1][7][8], these specific file paths and class names are standard conventions used across many web projects for mobile-responsive navigation rather than being unique to a single specific codebase [2][6][5][3].

Citations:


Wire the mobile toggle so CSS can reveal the sidebar.

.mobile-menu-toggle is styled, but there is no rendered toggle button and no JavaScript that toggles .sidebar.open or updates aria-expanded, so the mobile menu cannot open.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/css/responsive.css` around lines 25 - 46, Add a rendered
.mobile-menu-toggle button and client-side toggle behavior that targets
.sidebar, toggles its open class, and keeps aria-expanded synchronized with the
sidebar state; preserve the existing responsive CSS behavior and ensure the
control is accessible.

}

/* Table responsiveness */
@media (max-width: 768px) {
table {
display: block;
overflow-x: auto;
white-space: nowrap;
}
.dashboard-grid {
grid-template-columns: 1fr;
}
.card {
min-width: 0;
}
}

/* Print styles */
@media print {
.sidebar, .mobile-menu-toggle, .no-print {
display: none !important;
}
.main-content {
margin-left: 0 !important;
}
}

/* Mobile menu toggle */
.mobile-menu-toggle {
display: none;
position: fixed;
top: 1rem;
left: 1rem;
z-index: 1001;
background: var(--bg-primary, #fff);
border: 1px solid var(--border-color, #ddd);
border-radius: 4px;
padding: 0.5rem;
cursor: pointer;
}
70 changes: 70 additions & 0 deletions public/assets/js/accessibility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Accessibility enhancements — Closes #35
(function () {

Check failure on line 2 in public/assets/js/accessibility.js

View workflow job for this annotation

GitHub Actions / lint

Insert `;`
'use strict';

Check failure on line 3 in public/assets/js/accessibility.js

View workflow job for this annotation

GitHub Actions / lint

Delete `;`

function injectSkipLink() {
var skipLink = document.createElement('a');

Check failure on line 6 in public/assets/js/accessibility.js

View workflow job for this annotation

GitHub Actions / lint

Delete `;`
skipLink.href = '#main-content';

Check failure on line 7 in public/assets/js/accessibility.js

View workflow job for this annotation

GitHub Actions / lint

Delete `;`
skipLink.className = 'skip-link';

Check failure on line 8 in public/assets/js/accessibility.js

View workflow job for this annotation

GitHub Actions / lint

Delete `;`
skipLink.textContent = 'Skip to main content';

Check failure on line 9 in public/assets/js/accessibility.js

View workflow job for this annotation

GitHub Actions / lint

Delete `;`
document.body.prepend(skipLink);

Check failure on line 10 in public/assets/js/accessibility.js

View workflow job for this annotation

GitHub Actions / lint

Delete `;`
}

function injectLiveRegion() {
var region = document.createElement('div');

Check failure on line 14 in public/assets/js/accessibility.js

View workflow job for this annotation

GitHub Actions / lint

Delete `;`
region.id = 'aria-live-region';

Check failure on line 15 in public/assets/js/accessibility.js

View workflow job for this annotation

GitHub Actions / lint

Delete `;`
region.className = 'sr-only';

Check failure on line 16 in public/assets/js/accessibility.js

View workflow job for this annotation

GitHub Actions / lint

Delete `;`
region.setAttribute('aria-live', 'polite');
region.setAttribute('aria-atomic', 'true');
document.body.appendChild(region);
}

function setupFocusTraps() {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
mutation.addedNodes.forEach(function (node) {
if (node.nodeType !== 1) return;
var dialog = node.matches && node.matches('[role="dialog"], dialog')
? node
: node.querySelector && node.querySelector('[role="dialog"], dialog');
if (!dialog) return;

var focusable = dialog.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (focusable.length === 0) return;

var first = focusable[0];
var last = focusable[focusable.length - 1];

dialog.addEventListener('keydown', function (e) {
if (e.key !== 'Tab') return;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
});

setTimeout(function () { first.focus(); }, 100);
});
});
});

observer.observe(document.body, { childList: true, subtree: true });
Comment on lines +22 to +56

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Attach the focus trap to dialogs that already exist.

Line 56 observes only dialogs added after initialization. public/index.html Lines 1435-1448 already contains #security-modal, so this dialog never receives the keydown handler or initial focus when it opens. Initialize existing dialogs and activate the trap from the code that displays each dialog.

🧰 Tools
🪛 ast-grep (0.45.0)

[error] 50-50: React's useState should not be directly called
Context: setTimeout(function () { first.focus(); }, 100)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)

🪛 ESLint

[error] 26-26: Delete ;

(prettier/prettier)


[error] 27-27: Insert ⏎···········

(prettier/prettier)


[error] 28-28: Insert ··

(prettier/prettier)


[error] 29-29: Replace ············:·node.querySelector·&&·node.querySelector('[role="dialog"],·dialog'); with ··············:·node.querySelector·&&·node.querySelector('[role="dialog"],·dialog')

(prettier/prettier)


[error] 30-30: Delete ;

(prettier/prettier)


[error] 34-34: Delete ;

(prettier/prettier)


[error] 35-35: Delete ;

(prettier/prettier)


[error] 37-37: Delete ;

(prettier/prettier)


[error] 38-38: Delete ;

(prettier/prettier)


[error] 41-41: Delete ;

(prettier/prettier)


[error] 43-43: Delete ;

(prettier/prettier)


[error] 44-44: Delete ;

(prettier/prettier)


[error] 46-46: Delete ;

(prettier/prettier)


[error] 47-47: Delete ;

(prettier/prettier)


[error] 49-49: Delete ;

(prettier/prettier)


[error] 51-51: Replace ·first.focus();·},·100); with ⏎············first.focus()⏎··········},·100)

(prettier/prettier)


[error] 52-52: Delete ;

(prettier/prettier)


[error] 53-53: Delete ;

(prettier/prettier)


[error] 54-54: Delete ;

(prettier/prettier)


[error] 56-56: Delete ;

(prettier/prettier)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/js/accessibility.js` around lines 22 - 56, Update
setupFocusTraps to process dialogs already present in the document, including
`#security-modal`, instead of only handling newly added nodes. Reuse the existing
focusable-element, keydown-handler, and initial-focus logic for existing
dialogs, and invoke or connect this setup from each dialog’s display path so the
trap activates when the dialog opens.

}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}

function init() {
injectSkipLink();
injectLiveRegion();
setupFocusTraps();
Comment on lines +65 to +68

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 'accessibility\.(css|js)|responsive\.css' public

Repository: Flamki/stellarmind

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Tracked files under public:"
git ls-files public | sed -n '1,200p'

echo
echo "Candidate files of likely interest:"
git ls-files public | grep -Ei '(dashboard|layout|template|accessibility|responsive|\.css|\.js)$' | sed -n '1,200p'

echo
echo "All accessibility/responsive/search terms in tracked repo files:"
rg -n -C 2 'accessibility|responsive|skip-link|focus-trap|aria-live|live-region|skip link|Live Region|focus trap' . | sed -n '1,240p'

Repository: Flamki/stellarmind

Length of output: 6494


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "index.html head and script tags:"
rg -n -C 4 '<head>|</head>|<link|<script|init\(\\)|init\\(' public/index.html

echo
echo "Tracked files with public/index.html:"
python3 - <<'PY'
from pathlib import Path
for p in Path('.').rglob('*'):
    if p.is_file():
        try:
            s=p.read_text(errors='ignore')
        except Exception:
            continue
        if 'public/index.html' in s or s.count('index.html') > 0:
            continue
        if 'public/assets/css/accessibility.css' in s or 'public/assets/js/accessibility.js' in s or 'responsive.css' in s:
            print(p)
PY

Repository: Flamki/stellarmind

Length of output: 1406


Register the new accessibility assets in the dashboard HTML.

public/index.html includes no links to accessibility.css, responsive.css, or accessibility.js. Add the asset links and instantiate init() for public/assets/js/accessibility.js; otherwise the skip link, live region, responsive styles, and focus traps do not load.

🧰 Tools
🪛 ESLint

[error] 66-66: Delete ;

(prettier/prettier)


[error] 67-67: Delete ;

(prettier/prettier)


[error] 68-68: Delete ;

(prettier/prettier)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/js/accessibility.js` around lines 65 - 68, Update
public/index.html to include accessibility.css and responsive.css, load
public/assets/js/accessibility.js, and invoke its init() after the script loads
so the skip link, live region, and focus traps are initialized.

}
})();
Loading
Loading