Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
18 changes: 18 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,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