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
23 changes: 21 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

| Version | Supported |
| ------- | --------- |
| 0.1.x | ✅ |
| >=0.3.1 | ✅ |
| <0.3.1 | ❌ (deprecated) |

Skilleton CLI is currently pre-1.0, but we treat every tagged release as security-supported until a newer patch is published.
Versions earlier than `0.3.1` are deprecated due to security fixes (see the [Security Advisory](https://github.com/Fcmam5/skilleton/security/advisories/GHSA-5g3j-89fr-r2vp)). Please upgrade to `0.3.1` or later.
Comment on lines +7 to +10
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify security advisory URL and version 0.3.1 in package.json

# Check if the security advisory URL is accessible
echo "Checking security advisory URL..."
curl -s -o /dev/null -w "%{http_code}" https://github.com/Fcmam5/skilleton/security/advisories/GHSA-5g3j-89fr-r2vp

# Check if version 0.3.1 exists in package.json
echo -e "\nChecking version in package.json..."
if [ -f package.json ]; then
  cat package.json | jq -r '.version'
else
  echo "package.json not found"
fi

# Search for version references in the codebase
echo -e "\nSearching for version 0.3.1 references..."
rg -n "0\.3\.1" --type=json

Repository: Fcmam5/skilleton

Length of output: 540


Fix broken security advisory link and version mismatch in SECURITY.md.

Lines 7-10 reference a security advisory URL that returns a 404 error (GHSA-5g3j-89fr-r2vp), and the documentation recommends upgrading to version 0.3.1, but the current version in package.json is 0.3.0. Verify that the advisory URL is correct and update the version reference to match the actual released version.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SECURITY.md` around lines 7 - 10, Update SECURITY.md to point to the correct
security advisory URL and make the version recommendation consistent with
package.json: verify the advisory URL (replace the broken
https://github.com/Fcmam5/skilleton/security/advisories/GHSA-5g3j-89fr-r2vp with
the correct advisory link) and update the version text from `0.3.1` to the
actual released version (`0.3.0`) or, if the intent is to require `0.3.1`, bump
the version in package.json accordingly; ensure the table rows and the sentence
about upgrading use the same version string.


## Reporting a Vulnerability

Expand Down Expand Up @@ -35,6 +36,24 @@ Instead, email `au54vz9rk[at]mozmail[.]com` with the details. If you do not rece

We may invite you to test the fix before release if appropriate.

### Automated Alert Triage (GitHub)

We also monitor and triage GitHub security signals (CodeQL and Dependabot alerts).

- **Initial triage SLA**: within 7 days of an alert opening
- **Critical/High**: patch or documented mitigation target within 14 days
- **Medium**: patch target within 30 days
- **Low**: patch target within 90 days or explicitly risk-accepted with rationale

For each alert, we track:

- affected file/function and exploit preconditions
- real impact in Skilleton runtime/CLI context
- patch approach, owner, and target release
- verification (tests and/or reproduction before/after)

If an alert is a false positive or accepted risk, we record the reason and keep the decision reviewable.

## Scope

This policy covers:
Expand Down
42 changes: 32 additions & 10 deletions bin/skilleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,41 +20,63 @@ const commands: CommandRegistry = {
audit: new AuditCommand(),
};

function getCommandHandler(command: string): Command | null {
switch (command) {
case 'add':
return commands.add;
case 'install':
return commands.install;
case 'update':
return commands.update;
case 'list':
return commands.list;
case 'describe':
return commands.describe;
case 'audit':
return commands.audit;
default:
return null;
}
}

function parseArgs(argv: string[]): { command: string | null; args: CommandArgs } {
if (argv.length === 0) {
return { command: null, args: { positional: [], flags: {} } };
}

const [command, ...rest] = argv;
const tokens = [...rest];
const positional: string[] = [];
const flags: Record<string, string | boolean> = {};
const flagsMap = new Map<string, string | boolean>();

for (let i = 0; i < rest.length; i += 1) {
const token = rest[i];
while (tokens.length > 0) {
const token = tokens.shift()!;
if (token.startsWith('--')) {
const [rawFlag, value] = token.slice(2).split('=');
const flag = rawFlag === 'h' ? 'help' : rawFlag;
if (value !== undefined) {
flags[flag] = value;
flagsMap.set(flag, value);
continue;
}

const next = rest[i + 1];
const next = tokens[0];
if (next && !next.startsWith('-')) {
flags[flag] = next;
i += 1;
flagsMap.set(flag, next);
tokens.shift();
} else {
flags[flag] = true;
flagsMap.set(flag, true);
}
} else if (token.startsWith('-') && token.length > 1) {
const rawFlag = token.slice(1);
const flag = rawFlag === 'h' ? 'help' : rawFlag;
flags[flag] = true;
flagsMap.set(flag, true);
} else {
positional.push(token);
}
}

const flags = Object.fromEntries(flagsMap);

return { command, args: { positional, flags } };
}

Expand All @@ -81,7 +103,7 @@ async function main(): Promise<void> {
}

// Validate command first
const handler = commands[command];
const handler = getCommandHandler(command);
if (!handler) {
console.error(`Unknown command: ${command}`);
printHelp();
Expand Down
37 changes: 37 additions & 0 deletions eslint.config.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const tseslint = require('typescript-eslint');
const globals = require('globals');
const security = require('eslint-plugin-security');

const FILESYSTEM_IMPORT_MESSAGE = 'Use FileSystem from src/core/filesystem.ts instead of importing node:fs directly.';

module.exports = [
{
Expand All @@ -8,6 +11,9 @@ module.exports = [
...tseslint.configs.recommended,
{
files: ['**/*.ts'],
plugins: {
security,
},
languageOptions: {
globals: globals.node,
parser: tseslint.parser,
Expand All @@ -17,10 +23,41 @@ module.exports = [
},
},
rules: {
...security.configs.recommended.rules,
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'node:fs',
message: FILESYSTEM_IMPORT_MESSAGE,
},
{
name: 'node:fs/promises',
message: FILESYSTEM_IMPORT_MESSAGE,
},
{
name: 'fs',
message: FILESYSTEM_IMPORT_MESSAGE,
},
{
name: 'fs/promises',
message: FILESYSTEM_IMPORT_MESSAGE,
},
],
},
],
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
},
},
{
files: ['src/core/filesystem.ts'],
rules: {
'no-restricted-imports': 'off',
'security/detect-non-literal-fs-filename': 'off', // We expect controlled/validated inputs
},
},
{
files: ['eslint.config.cjs'],
rules: {
Expand Down
1 change: 1 addition & 0 deletions jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const config = {
'^.+\\.(ts|tsx)$': 'ts-jest',
},
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts'],
};

module.exports = config;
Loading
Loading