@neurcode-ai/cli 0.9.64 → 0.9.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/commands/brain.d.ts.map +1 -1
- package/dist/commands/brain.js +273 -0
- package/dist/commands/brain.js.map +1 -1
- package/dist/commands/pilot-report.d.ts +9 -0
- package/dist/commands/pilot-report.d.ts.map +1 -0
- package/dist/commands/pilot-report.js +176 -0
- package/dist/commands/pilot-report.js.map +1 -0
- package/dist/commands/remediate-governance.d.ts +54 -0
- package/dist/commands/remediate-governance.d.ts.map +1 -0
- package/dist/commands/remediate-governance.js +375 -0
- package/dist/commands/remediate-governance.js.map +1 -0
- package/dist/commands/remediate.d.ts.map +1 -1
- package/dist/commands/remediate.js.map +1 -1
- package/dist/commands/replay.d.ts.map +1 -1
- package/dist/commands/replay.js +30 -0
- package/dist/commands/replay.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +307 -24
- package/dist/commands/verify.js.map +1 -1
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +1078 -0
- package/dist/daemon/server.js.map +1 -1
- package/dist/explainability/DeterminismClassifier.d.ts +34 -0
- package/dist/explainability/DeterminismClassifier.d.ts.map +1 -0
- package/dist/explainability/DeterminismClassifier.js +104 -0
- package/dist/explainability/DeterminismClassifier.js.map +1 -0
- package/dist/explainability/ViolationFormatter.d.ts +32 -0
- package/dist/explainability/ViolationFormatter.d.ts.map +1 -0
- package/dist/explainability/ViolationFormatter.js +252 -0
- package/dist/explainability/ViolationFormatter.js.map +1 -0
- package/dist/explainability/index.d.ts +15 -0
- package/dist/explainability/index.d.ts.map +1 -0
- package/dist/explainability/index.js +94 -0
- package/dist/explainability/index.js.map +1 -0
- package/dist/explainability/types.d.ts +37 -0
- package/dist/explainability/types.d.ts.map +1 -0
- package/dist/explainability/types.js +3 -0
- package/dist/explainability/types.js.map +1 -0
- package/dist/governance/canonical-pipeline.d.ts +38 -0
- package/dist/governance/canonical-pipeline.d.ts.map +1 -0
- package/dist/governance/canonical-pipeline.js +448 -0
- package/dist/governance/canonical-pipeline.js.map +1 -0
- package/dist/governance/structural-on-diff.d.ts +13 -0
- package/dist/governance/structural-on-diff.d.ts.map +1 -0
- package/dist/governance/structural-on-diff.js +35 -0
- package/dist/governance/structural-on-diff.js.map +1 -0
- package/dist/governance/structural-policy-merge.d.ts +14 -0
- package/dist/governance/structural-policy-merge.d.ts.map +1 -0
- package/dist/governance/structural-policy-merge.js +25 -0
- package/dist/governance/structural-policy-merge.js.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/review-compression/index.d.ts +50 -0
- package/dist/integrations/review-compression/index.d.ts.map +1 -0
- package/dist/integrations/review-compression/index.js +158 -0
- package/dist/integrations/review-compression/index.js.map +1 -0
- package/dist/intent-engine/domain-taxonomy.d.ts +42 -0
- package/dist/intent-engine/domain-taxonomy.d.ts.map +1 -0
- package/dist/intent-engine/domain-taxonomy.js +534 -0
- package/dist/intent-engine/domain-taxonomy.js.map +1 -0
- package/dist/intent-engine/index.d.ts +1 -0
- package/dist/intent-engine/index.d.ts.map +1 -1
- package/dist/intent-engine/index.js +6 -1
- package/dist/intent-engine/index.js.map +1 -1
- package/dist/intent-engine/parser.d.ts.map +1 -1
- package/dist/intent-engine/parser.js +47 -0
- package/dist/intent-engine/parser.js.map +1 -1
- package/dist/intent-engine/semantic-expander.d.ts +104 -0
- package/dist/intent-engine/semantic-expander.d.ts.map +1 -0
- package/dist/intent-engine/semantic-expander.js +480 -0
- package/dist/intent-engine/semantic-expander.js.map +1 -0
- package/dist/patch-engine/patterns.d.ts.map +1 -1
- package/dist/patch-engine/patterns.js +8 -4
- package/dist/patch-engine/patterns.js.map +1 -1
- package/dist/semantic/index.d.ts +14 -0
- package/dist/semantic/index.d.ts.map +1 -0
- package/dist/semantic/index.js +30 -0
- package/dist/semantic/index.js.map +1 -0
- package/dist/semantic/tfidf-engine.d.ts +81 -0
- package/dist/semantic/tfidf-engine.d.ts.map +1 -0
- package/dist/semantic/tfidf-engine.js +278 -0
- package/dist/semantic/tfidf-engine.js.map +1 -0
- package/dist/semantic/vector-store.d.ts +108 -0
- package/dist/semantic/vector-store.d.ts.map +1 -0
- package/dist/semantic/vector-store.js +321 -0
- package/dist/semantic/vector-store.js.map +1 -0
- package/dist/structural-rules/context-severity.d.ts +46 -0
- package/dist/structural-rules/context-severity.d.ts.map +1 -0
- package/dist/structural-rules/context-severity.js +115 -0
- package/dist/structural-rules/context-severity.js.map +1 -0
- package/dist/structural-rules/distributed/DS001-saga-rollback-absence.d.ts +11 -0
- package/dist/structural-rules/distributed/DS001-saga-rollback-absence.d.ts.map +1 -0
- package/dist/structural-rules/distributed/DS001-saga-rollback-absence.js +212 -0
- package/dist/structural-rules/distributed/DS001-saga-rollback-absence.js.map +1 -0
- package/dist/structural-rules/distributed/DS002-missing-correlation-id.d.ts +11 -0
- package/dist/structural-rules/distributed/DS002-missing-correlation-id.d.ts.map +1 -0
- package/dist/structural-rules/distributed/DS002-missing-correlation-id.js +213 -0
- package/dist/structural-rules/distributed/DS002-missing-correlation-id.js.map +1 -0
- package/dist/structural-rules/distributed/index.d.ts +3 -0
- package/dist/structural-rules/distributed/index.d.ts.map +1 -0
- package/dist/structural-rules/distributed/index.js +8 -0
- package/dist/structural-rules/distributed/index.js.map +1 -0
- package/dist/structural-rules/engine.d.ts +25 -0
- package/dist/structural-rules/engine.d.ts.map +1 -0
- package/dist/structural-rules/engine.js +90 -0
- package/dist/structural-rules/engine.js.map +1 -0
- package/dist/structural-rules/index.d.ts +41 -0
- package/dist/structural-rules/index.d.ts.map +1 -0
- package/dist/structural-rules/index.js +141 -0
- package/dist/structural-rules/index.js.map +1 -0
- package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.d.ts +11 -0
- package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.d.ts.map +1 -0
- package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.js +66 -0
- package/dist/structural-rules/python/PY001-asyncio-task-without-cancel.js.map +1 -0
- package/dist/structural-rules/python/PY002-unbounded-dict-singleton.d.ts +11 -0
- package/dist/structural-rules/python/PY002-unbounded-dict-singleton.d.ts.map +1 -0
- package/dist/structural-rules/python/PY002-unbounded-dict-singleton.js +135 -0
- package/dist/structural-rules/python/PY002-unbounded-dict-singleton.js.map +1 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.d.ts +11 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.d.ts.map +1 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.js +86 -0
- package/dist/structural-rules/python/PY003-broad-except-clause.js.map +1 -0
- package/dist/structural-rules/python/PY004-swallowed-async-exception.d.ts +11 -0
- package/dist/structural-rules/python/PY004-swallowed-async-exception.d.ts.map +1 -0
- package/dist/structural-rules/python/PY004-swallowed-async-exception.js +167 -0
- package/dist/structural-rules/python/PY004-swallowed-async-exception.js.map +1 -0
- package/dist/structural-rules/python/PY005-fastapi-without-pydantic.d.ts +11 -0
- package/dist/structural-rules/python/PY005-fastapi-without-pydantic.d.ts.map +1 -0
- package/dist/structural-rules/python/PY005-fastapi-without-pydantic.js +154 -0
- package/dist/structural-rules/python/PY005-fastapi-without-pydantic.js.map +1 -0
- package/dist/structural-rules/python/PY006-blocking-io-in-async.d.ts +11 -0
- package/dist/structural-rules/python/PY006-blocking-io-in-async.d.ts.map +1 -0
- package/dist/structural-rules/python/PY006-blocking-io-in-async.js +130 -0
- package/dist/structural-rules/python/PY006-blocking-io-in-async.js.map +1 -0
- package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.d.ts +11 -0
- package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.d.ts.map +1 -0
- package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.js +93 -0
- package/dist/structural-rules/python/PY007-sqlalchemy-session-leak.js.map +1 -0
- package/dist/structural-rules/python/PY008-celery-task-without-retry.d.ts +11 -0
- package/dist/structural-rules/python/PY008-celery-task-without-retry.d.ts.map +1 -0
- package/dist/structural-rules/python/PY008-celery-task-without-retry.js +154 -0
- package/dist/structural-rules/python/PY008-celery-task-without-retry.js.map +1 -0
- package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.d.ts +11 -0
- package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.d.ts.map +1 -0
- package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.js +133 -0
- package/dist/structural-rules/python/PY009-unsafe-pickle-deserialization.js.map +1 -0
- package/dist/structural-rules/python/PY010-leaked-aiohttp-session.d.ts +11 -0
- package/dist/structural-rules/python/PY010-leaked-aiohttp-session.d.ts.map +1 -0
- package/dist/structural-rules/python/PY010-leaked-aiohttp-session.js +80 -0
- package/dist/structural-rules/python/PY010-leaked-aiohttp-session.js.map +1 -0
- package/dist/structural-rules/rules/SR001-swallowed-async-rejection.d.ts +11 -0
- package/dist/structural-rules/rules/SR001-swallowed-async-rejection.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR001-swallowed-async-rejection.js +145 -0
- package/dist/structural-rules/rules/SR001-swallowed-async-rejection.js.map +1 -0
- package/dist/structural-rules/rules/SR002-unbounded-collection.d.ts +11 -0
- package/dist/structural-rules/rules/SR002-unbounded-collection.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR002-unbounded-collection.js +196 -0
- package/dist/structural-rules/rules/SR002-unbounded-collection.js.map +1 -0
- package/dist/structural-rules/rules/SR003-timer-without-cleanup.d.ts +11 -0
- package/dist/structural-rules/rules/SR003-timer-without-cleanup.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR003-timer-without-cleanup.js +148 -0
- package/dist/structural-rules/rules/SR003-timer-without-cleanup.js.map +1 -0
- package/dist/structural-rules/rules/SR004-request-boundary-no-validation.d.ts +11 -0
- package/dist/structural-rules/rules/SR004-request-boundary-no-validation.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR004-request-boundary-no-validation.js +162 -0
- package/dist/structural-rules/rules/SR004-request-boundary-no-validation.js.map +1 -0
- package/dist/structural-rules/rules/SR005-halfopen-probe-gate.d.ts +11 -0
- package/dist/structural-rules/rules/SR005-halfopen-probe-gate.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR005-halfopen-probe-gate.js +150 -0
- package/dist/structural-rules/rules/SR005-halfopen-probe-gate.js.map +1 -0
- package/dist/structural-rules/rules/SR006-fanout-error-sanitization.d.ts +11 -0
- package/dist/structural-rules/rules/SR006-fanout-error-sanitization.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR006-fanout-error-sanitization.js +161 -0
- package/dist/structural-rules/rules/SR006-fanout-error-sanitization.js.map +1 -0
- package/dist/structural-rules/rules/SR007-cross-request-error.d.ts +11 -0
- package/dist/structural-rules/rules/SR007-cross-request-error.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR007-cross-request-error.js +175 -0
- package/dist/structural-rules/rules/SR007-cross-request-error.js.map +1 -0
- package/dist/structural-rules/rules/SR008-background-task-orphan.d.ts +11 -0
- package/dist/structural-rules/rules/SR008-background-task-orphan.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR008-background-task-orphan.js +176 -0
- package/dist/structural-rules/rules/SR008-background-task-orphan.js.map +1 -0
- package/dist/structural-rules/rules/SR009-missing-retry-backoff.d.ts +11 -0
- package/dist/structural-rules/rules/SR009-missing-retry-backoff.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR009-missing-retry-backoff.js +168 -0
- package/dist/structural-rules/rules/SR009-missing-retry-backoff.js.map +1 -0
- package/dist/structural-rules/rules/SR010-retry-storm.d.ts +11 -0
- package/dist/structural-rules/rules/SR010-retry-storm.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR010-retry-storm.js +181 -0
- package/dist/structural-rules/rules/SR010-retry-storm.js.map +1 -0
- package/dist/structural-rules/rules/SR011-event-listener-leak.d.ts +11 -0
- package/dist/structural-rules/rules/SR011-event-listener-leak.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR011-event-listener-leak.js +208 -0
- package/dist/structural-rules/rules/SR011-event-listener-leak.js.map +1 -0
- package/dist/structural-rules/rules/SR012-promise-race-leak.d.ts +11 -0
- package/dist/structural-rules/rules/SR012-promise-race-leak.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR012-promise-race-leak.js +191 -0
- package/dist/structural-rules/rules/SR012-promise-race-leak.js.map +1 -0
- package/dist/structural-rules/rules/SR013-missing-idempotency-key.d.ts +11 -0
- package/dist/structural-rules/rules/SR013-missing-idempotency-key.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR013-missing-idempotency-key.js +219 -0
- package/dist/structural-rules/rules/SR013-missing-idempotency-key.js.map +1 -0
- package/dist/structural-rules/rules/SR014-mutable-closure-async.d.ts +11 -0
- package/dist/structural-rules/rules/SR014-mutable-closure-async.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR014-mutable-closure-async.js +208 -0
- package/dist/structural-rules/rules/SR014-mutable-closure-async.js.map +1 -0
- package/dist/structural-rules/rules/SR015-dangling-abort-controller.d.ts +11 -0
- package/dist/structural-rules/rules/SR015-dangling-abort-controller.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR015-dangling-abort-controller.js +190 -0
- package/dist/structural-rules/rules/SR015-dangling-abort-controller.js.map +1 -0
- package/dist/structural-rules/rules/SR016-unsafe-json-parse.d.ts +11 -0
- package/dist/structural-rules/rules/SR016-unsafe-json-parse.d.ts.map +1 -0
- package/dist/structural-rules/rules/SR016-unsafe-json-parse.js +187 -0
- package/dist/structural-rules/rules/SR016-unsafe-json-parse.js.map +1 -0
- package/dist/structural-rules/suppressions.d.ts +43 -0
- package/dist/structural-rules/suppressions.d.ts.map +1 -0
- package/dist/structural-rules/suppressions.js +115 -0
- package/dist/structural-rules/suppressions.js.map +1 -0
- package/dist/structural-rules/types.d.ts +43 -0
- package/dist/structural-rules/types.d.ts.map +1 -0
- package/dist/structural-rules/types.js +3 -0
- package/dist/structural-rules/types.js.map +1 -0
- package/dist/utils/brain-cache.d.ts +100 -0
- package/dist/utils/brain-cache.d.ts.map +1 -0
- package/dist/utils/brain-cache.js +346 -0
- package/dist/utils/brain-cache.js.map +1 -0
- package/dist/utils/governance-provenance.d.ts +95 -0
- package/dist/utils/governance-provenance.d.ts.map +1 -0
- package/dist/utils/governance-provenance.js +187 -0
- package/dist/utils/governance-provenance.js.map +1 -0
- package/dist/utils/pilot-metrics.d.ts +46 -0
- package/dist/utils/pilot-metrics.d.ts.map +1 -0
- package/dist/utils/pilot-metrics.js +240 -0
- package/dist/utils/pilot-metrics.js.map +1 -0
- package/dist/utils/replay-runtime.d.ts +34 -0
- package/dist/utils/replay-runtime.d.ts.map +1 -1
- package/dist/utils/replay-runtime.js +207 -0
- package/dist/utils/replay-runtime.js.map +1 -1
- package/dist/workspace/cross-repo-graph.d.ts +111 -0
- package/dist/workspace/cross-repo-graph.d.ts.map +1 -0
- package/dist/workspace/cross-repo-graph.js +450 -0
- package/dist/workspace/cross-repo-graph.js.map +1 -0
- package/dist/workspace/federated-context.d.ts +144 -0
- package/dist/workspace/federated-context.d.ts.map +1 -0
- package/dist/workspace/federated-context.js +347 -0
- package/dist/workspace/federated-context.js.map +1 -0
- package/dist/workspace/index.d.ts +38 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +48 -0
- package/dist/workspace/index.js.map +1 -0
- package/package.json +9 -9
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PY008CeleryTaskWithoutRetry = void 0;
|
|
4
|
+
// Matches Celery task decorators
|
|
5
|
+
const CELERY_DECORATOR_RE = /^\s*@(?:\w+\.)?(?:app\.task|celery\.task|shared_task)\s*[\(\n]/;
|
|
6
|
+
const CELERY_DECORATOR_INLINE_RE = /^\s*@(?:\w+\.)?(?:app\.task|celery\.task|shared_task)\s*\(/;
|
|
7
|
+
// Retry configuration keywords inside the decorator
|
|
8
|
+
const RETRY_CONFIG_RE = /(?:max_retries|retry_backoff|autoretry_for|bind\s*=\s*True)/;
|
|
9
|
+
// ignore_result=True — fire-and-forget, valid without retry
|
|
10
|
+
const IGNORE_RESULT_RE = /ignore_result\s*=\s*True/;
|
|
11
|
+
// A raise statement inside the function body
|
|
12
|
+
const RAISE_RE = /\braise\b/;
|
|
13
|
+
// self.retry( call — manual retry in bind=True task
|
|
14
|
+
const SELF_RETRY_RE = /\bself\.retry\s*\(/;
|
|
15
|
+
function getIndent(line) {
|
|
16
|
+
return line.length - line.trimStart().length;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Collect the decorator text (potentially multi-line) starting at decoratorLine.
|
|
20
|
+
* Returns the full decorator string and the line index where the decorator ends.
|
|
21
|
+
*/
|
|
22
|
+
function collectDecorator(lines, decoratorLine) {
|
|
23
|
+
let text = lines[decoratorLine];
|
|
24
|
+
let depth = 0;
|
|
25
|
+
for (const ch of lines[decoratorLine]) {
|
|
26
|
+
if (ch === '(')
|
|
27
|
+
depth++;
|
|
28
|
+
else if (ch === ')')
|
|
29
|
+
depth--;
|
|
30
|
+
}
|
|
31
|
+
let j = decoratorLine + 1;
|
|
32
|
+
while (depth > 0 && j < lines.length) {
|
|
33
|
+
text += '\n' + lines[j];
|
|
34
|
+
for (const ch of lines[j]) {
|
|
35
|
+
if (ch === '(')
|
|
36
|
+
depth++;
|
|
37
|
+
else if (ch === ')')
|
|
38
|
+
depth--;
|
|
39
|
+
}
|
|
40
|
+
j++;
|
|
41
|
+
}
|
|
42
|
+
return { text, endLine: j - 1 };
|
|
43
|
+
}
|
|
44
|
+
class PY008CeleryTaskWithoutRetry {
|
|
45
|
+
id = 'PY008';
|
|
46
|
+
name = 'Celery task without retry configuration';
|
|
47
|
+
policyRef = 'PY008';
|
|
48
|
+
severity = 'ADVISORY';
|
|
49
|
+
languages = ['python'];
|
|
50
|
+
description = 'Celery task functions that can raise exceptions but have no retry configuration silently drop jobs on transient failures.';
|
|
51
|
+
check(filePath, sourceText) {
|
|
52
|
+
try {
|
|
53
|
+
const violations = [];
|
|
54
|
+
// Normalize line endings
|
|
55
|
+
const lines = sourceText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
|
|
56
|
+
let i = 0;
|
|
57
|
+
while (i < lines.length) {
|
|
58
|
+
const line = lines[i];
|
|
59
|
+
// Detect Celery decorator
|
|
60
|
+
const isDecorator = CELERY_DECORATOR_RE.test(line) || CELERY_DECORATOR_INLINE_RE.test(line);
|
|
61
|
+
if (!isDecorator) {
|
|
62
|
+
i++;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const decoratorStartLine = i;
|
|
66
|
+
// Collect full decorator text (handles multi-line)
|
|
67
|
+
const { text: decoratorText, endLine: decoratorEnd } = collectDecorator(lines, i);
|
|
68
|
+
// Check for retry config
|
|
69
|
+
const hasRetryConfig = RETRY_CONFIG_RE.test(decoratorText);
|
|
70
|
+
const hasIgnoreResult = IGNORE_RESULT_RE.test(decoratorText);
|
|
71
|
+
// Find the function definition line after decorator
|
|
72
|
+
let funcDefLine = -1;
|
|
73
|
+
let j = decoratorEnd + 1;
|
|
74
|
+
while (j < Math.min(decoratorEnd + 6, lines.length)) {
|
|
75
|
+
const l = lines[j].trimStart();
|
|
76
|
+
if (/^(?:async\s+)?def\s+\w+\s*\(/.test(l)) {
|
|
77
|
+
funcDefLine = j;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
if (l.length > 0 && !l.startsWith('@') && !l.startsWith('#'))
|
|
81
|
+
break;
|
|
82
|
+
j++;
|
|
83
|
+
}
|
|
84
|
+
if (funcDefLine === -1) {
|
|
85
|
+
i = j;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (hasRetryConfig) {
|
|
89
|
+
// Already has retry config — no violation
|
|
90
|
+
i = funcDefLine + 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// Collect function body
|
|
94
|
+
const funcIndent = getIndent(lines[funcDefLine]);
|
|
95
|
+
let bodyHasRaise = false;
|
|
96
|
+
let bodyHasSelfRetry = false;
|
|
97
|
+
let k = funcDefLine + 1;
|
|
98
|
+
while (k < lines.length) {
|
|
99
|
+
const bl = lines[k];
|
|
100
|
+
const bt = bl.trimStart();
|
|
101
|
+
if (bt.length === 0) {
|
|
102
|
+
k++;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const bi = getIndent(bl);
|
|
106
|
+
if (bi <= funcIndent)
|
|
107
|
+
break;
|
|
108
|
+
if (RAISE_RE.test(bl))
|
|
109
|
+
bodyHasRaise = true;
|
|
110
|
+
if (SELF_RETRY_RE.test(bl))
|
|
111
|
+
bodyHasSelfRetry = true;
|
|
112
|
+
k++;
|
|
113
|
+
}
|
|
114
|
+
// If fire-and-forget (ignore_result=True) and no raise → no violation
|
|
115
|
+
if (hasIgnoreResult && !bodyHasRaise) {
|
|
116
|
+
i = k;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// If uses self.retry() manually → no violation
|
|
120
|
+
if (bodyHasSelfRetry) {
|
|
121
|
+
i = k;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
// If function has potential raises and no retry config → flag it
|
|
125
|
+
if (bodyHasRaise) {
|
|
126
|
+
violations.push({
|
|
127
|
+
ruleId: this.id,
|
|
128
|
+
ruleName: this.name,
|
|
129
|
+
policyRef: this.policyRef,
|
|
130
|
+
severity: this.severity,
|
|
131
|
+
filePath,
|
|
132
|
+
line: decoratorStartLine + 1,
|
|
133
|
+
column: 1,
|
|
134
|
+
evidence: lines[decoratorStartLine].slice(0, 120),
|
|
135
|
+
operationalRisk: 'A transient failure (network timeout, DB connection error) in a Celery task without retry configuration ' +
|
|
136
|
+
'permanently drops the job. The message is lost without processing, causing data loss or inconsistent state.',
|
|
137
|
+
remediation: 'Add `autoretry_for=(Exception,), max_retries=3, retry_backoff=True` to the decorator, ' +
|
|
138
|
+
'or use `self.retry(exc=exc, countdown=2**self.request.retries)` in the exception handler.',
|
|
139
|
+
determinism: 'heuristic-advisory',
|
|
140
|
+
confidence: 0.75,
|
|
141
|
+
language: 'python',
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
i = k;
|
|
145
|
+
}
|
|
146
|
+
return violations;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
exports.PY008CeleryTaskWithoutRetry = PY008CeleryTaskWithoutRetry;
|
|
154
|
+
//# sourceMappingURL=PY008-celery-task-without-retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY008-celery-task-without-retry.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY008-celery-task-without-retry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
|
|
2
|
+
export declare class PY009UnsafePickleDeserialization implements StructuralRule {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
policyRef: string;
|
|
6
|
+
severity: "BLOCKING";
|
|
7
|
+
languages: RuleLanguage[];
|
|
8
|
+
description: string;
|
|
9
|
+
check(filePath: string, sourceText: string): StructuralViolation[];
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=PY009-unsafe-pickle-deserialization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY009-unsafe-pickle-deserialization.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY009-unsafe-pickle-deserialization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAmB7E,qBAAa,gCAAiC,YAAW,cAAc;IACrE,EAAE,SAAW;IACb,IAAI,SAAmC;IACvC,SAAS,SAAW;IACpB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SAEsD;IAEjE,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAoHnE"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PY009UnsafePickleDeserialization = void 0;
|
|
4
|
+
// Matches pickle.loads( or pickle.load(
|
|
5
|
+
const PICKLE_LOAD_RE = /\bpickle\.loads?\s*\(/;
|
|
6
|
+
// Matches joblib.load(
|
|
7
|
+
const JOBLIB_LOAD_RE = /\bjoblib\.load\s*\(/;
|
|
8
|
+
// Matches torch.load( without weights_only=True
|
|
9
|
+
const TORCH_LOAD_RE = /\btorch\.load\s*\(/;
|
|
10
|
+
const TORCH_WEIGHTS_ONLY_RE = /weights_only\s*=\s*True/;
|
|
11
|
+
// Detects if this appears to be a test file
|
|
12
|
+
const TEST_FILE_RE = /(?:^|[\\/])(?:test_|_test|tests[\\/])/;
|
|
13
|
+
// Detects if the pickle input looks like a literal bytes value in a test
|
|
14
|
+
// e.g. pickle.loads(b'\x80\x04...') or pickle.loads(b"...")
|
|
15
|
+
const LITERAL_BYTES_ARG_RE = /\bpickle\.loads?\s*\(\s*b['"]|pickle\.loads?\s*\(\s*b"""|\bpickle\.loads?\s*\(\s*b'''/;
|
|
16
|
+
class PY009UnsafePickleDeserialization {
|
|
17
|
+
id = 'PY009';
|
|
18
|
+
name = 'Unsafe pickle deserialization';
|
|
19
|
+
policyRef = 'PY009';
|
|
20
|
+
severity = 'BLOCKING';
|
|
21
|
+
languages = ['python'];
|
|
22
|
+
description = 'pickle.loads() / pickle.load() executes arbitrary Python code during deserialization. ' +
|
|
23
|
+
'torch.load() without weights_only=True is equally dangerous.';
|
|
24
|
+
check(filePath, sourceText) {
|
|
25
|
+
try {
|
|
26
|
+
const violations = [];
|
|
27
|
+
// Normalize line endings
|
|
28
|
+
const lines = sourceText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
|
|
29
|
+
const isTestFile = TEST_FILE_RE.test(filePath);
|
|
30
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31
|
+
const line = lines[i];
|
|
32
|
+
const trimmed = line.trimStart();
|
|
33
|
+
// Skip comment lines
|
|
34
|
+
if (trimmed.startsWith('#'))
|
|
35
|
+
continue;
|
|
36
|
+
// Skip noqa lines
|
|
37
|
+
if (/\bnoqa\b/.test(line))
|
|
38
|
+
continue;
|
|
39
|
+
// Check pickle.loads / pickle.load
|
|
40
|
+
if (PICKLE_LOAD_RE.test(line)) {
|
|
41
|
+
// Exclude: test file with literal bytes argument
|
|
42
|
+
if (isTestFile && LITERAL_BYTES_ARG_RE.test(line)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
violations.push({
|
|
46
|
+
ruleId: this.id,
|
|
47
|
+
ruleName: this.name,
|
|
48
|
+
policyRef: this.policyRef,
|
|
49
|
+
severity: this.severity,
|
|
50
|
+
filePath,
|
|
51
|
+
line: i + 1,
|
|
52
|
+
column: 1,
|
|
53
|
+
evidence: line.slice(0, 120),
|
|
54
|
+
operationalRisk: '`pickle.loads()` executes arbitrary Python code during deserialization. ' +
|
|
55
|
+
'A single compromised or malformed pickle payload from any source achieves remote code execution ' +
|
|
56
|
+
'on the deserializing machine. This is a critical supply-chain attack vector in ML systems that share model artifacts.',
|
|
57
|
+
remediation: 'Replace `pickle` with `json`, `msgpack`, or `protobuf` for data serialization. ' +
|
|
58
|
+
'For ML models, use `safetensors` format. If pickle is truly required, validate the HMAC signature ' +
|
|
59
|
+
'before deserializing and only accept pickles from trusted, authenticated internal sources.',
|
|
60
|
+
determinism: 'heuristic-advisory',
|
|
61
|
+
confidence: 0.95,
|
|
62
|
+
language: 'python',
|
|
63
|
+
});
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Check joblib.load(
|
|
67
|
+
if (JOBLIB_LOAD_RE.test(line)) {
|
|
68
|
+
violations.push({
|
|
69
|
+
ruleId: this.id,
|
|
70
|
+
ruleName: this.name,
|
|
71
|
+
policyRef: this.policyRef,
|
|
72
|
+
severity: this.severity,
|
|
73
|
+
filePath,
|
|
74
|
+
line: i + 1,
|
|
75
|
+
column: 1,
|
|
76
|
+
evidence: line.slice(0, 120),
|
|
77
|
+
operationalRisk: '`joblib.load()` uses pickle internally and executes arbitrary Python code during deserialization. ' +
|
|
78
|
+
'Malicious or tampered model artifacts can achieve remote code execution.',
|
|
79
|
+
remediation: 'Use `safetensors` format for model artifacts, or validate the HMAC signature of the joblib file before loading.',
|
|
80
|
+
determinism: 'heuristic-advisory',
|
|
81
|
+
confidence: 0.95,
|
|
82
|
+
language: 'python',
|
|
83
|
+
});
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
// Check torch.load( — flag if weights_only=True is NOT on the same line
|
|
87
|
+
// Also check the next 2 lines for multi-line calls
|
|
88
|
+
if (TORCH_LOAD_RE.test(line)) {
|
|
89
|
+
// Collect the call: check current line + next 2 for weights_only=True
|
|
90
|
+
let callText = line;
|
|
91
|
+
for (let k = 1; k <= 2 && i + k < lines.length; k++) {
|
|
92
|
+
callText += '\n' + lines[i + k];
|
|
93
|
+
// Stop if we've closed the parens
|
|
94
|
+
let depth = 0;
|
|
95
|
+
for (const ch of callText) {
|
|
96
|
+
if (ch === '(')
|
|
97
|
+
depth++;
|
|
98
|
+
else if (ch === ')')
|
|
99
|
+
depth--;
|
|
100
|
+
}
|
|
101
|
+
if (depth <= 0)
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
if (!TORCH_WEIGHTS_ONLY_RE.test(callText)) {
|
|
105
|
+
violations.push({
|
|
106
|
+
ruleId: this.id,
|
|
107
|
+
ruleName: this.name,
|
|
108
|
+
policyRef: this.policyRef,
|
|
109
|
+
severity: this.severity,
|
|
110
|
+
filePath,
|
|
111
|
+
line: i + 1,
|
|
112
|
+
column: 1,
|
|
113
|
+
evidence: line.slice(0, 120),
|
|
114
|
+
operationalRisk: '`torch.load()` without `weights_only=True` uses pickle and executes arbitrary Python code. ' +
|
|
115
|
+
'PyTorch 2.0+ requires `weights_only=True` for safe model loading from untrusted sources.',
|
|
116
|
+
remediation: 'Add `weights_only=True`: `torch.load(path, weights_only=True)`. ' +
|
|
117
|
+
'For full model loading you trust internally, at minimum validate the source integrity before loading.',
|
|
118
|
+
determinism: 'heuristic-advisory',
|
|
119
|
+
confidence: 0.95,
|
|
120
|
+
language: 'python',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return violations;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.PY009UnsafePickleDeserialization = PY009UnsafePickleDeserialization;
|
|
133
|
+
//# sourceMappingURL=PY009-unsafe-pickle-deserialization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY009-unsafe-pickle-deserialization.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY009-unsafe-pickle-deserialization.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
|
|
2
|
+
export declare class PY010LeakedAiohttpSession implements StructuralRule {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
policyRef: string;
|
|
6
|
+
severity: "BLOCKING";
|
|
7
|
+
languages: RuleLanguage[];
|
|
8
|
+
description: string;
|
|
9
|
+
check(filePath: string, sourceText: string): StructuralViolation[];
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=PY010-leaked-aiohttp-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY010-leaked-aiohttp-session.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY010-leaked-aiohttp-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAY7E,qBAAa,yBAA0B,YAAW,cAAc;IAC9D,EAAE,SAAW;IACb,IAAI,SAA2D;IAC/D,SAAS,SAAW;IACpB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SAC8G;IAEzH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAiEnE"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PY010LeakedAiohttpSession = void 0;
|
|
4
|
+
// Matches bare assignment: session = aiohttp.ClientSession() or session = ClientSession()
|
|
5
|
+
// Captures variable name and whether it's aiohttp-qualified
|
|
6
|
+
const SESSION_ASSIGN_RE = /^(\s*)(\w+)\s*=\s*(?:aiohttp\.)?ClientSession\s*\(/;
|
|
7
|
+
// Matches correct async with usage
|
|
8
|
+
const ASYNC_WITH_SESSION_RE = /^\s*async\s+with\s+(?:aiohttp\.)?ClientSession\s*\(/;
|
|
9
|
+
// Matches await <varname>.close() — shutdown hook pattern
|
|
10
|
+
const AWAIT_CLOSE_RE = /\bawait\s+\w+\s*\.\s*close\s*\(\)/;
|
|
11
|
+
class PY010LeakedAiohttpSession {
|
|
12
|
+
id = 'PY010';
|
|
13
|
+
name = 'aiohttp.ClientSession created without context manager';
|
|
14
|
+
policyRef = 'PY010';
|
|
15
|
+
severity = 'BLOCKING';
|
|
16
|
+
languages = ['python'];
|
|
17
|
+
description = 'aiohttp.ClientSession() assigned to a variable without `async with` leaks TCP connection pools and file descriptors.';
|
|
18
|
+
check(filePath, sourceText) {
|
|
19
|
+
try {
|
|
20
|
+
const violations = [];
|
|
21
|
+
// Normalize line endings
|
|
22
|
+
const normalizedText = sourceText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
23
|
+
const lines = normalizedText.split('\n');
|
|
24
|
+
// Pre-scan: does the file contain await <something>.close() anywhere?
|
|
25
|
+
// This handles the module-level singleton pattern with a shutdown hook.
|
|
26
|
+
const fileHasAwaitClose = AWAIT_CLOSE_RE.test(normalizedText);
|
|
27
|
+
for (let i = 0; i < lines.length; i++) {
|
|
28
|
+
const line = lines[i];
|
|
29
|
+
const trimmed = line.trimStart();
|
|
30
|
+
// Skip comment lines
|
|
31
|
+
if (trimmed.startsWith('#'))
|
|
32
|
+
continue;
|
|
33
|
+
// Skip noqa
|
|
34
|
+
if (/\bnoqa\b/.test(line))
|
|
35
|
+
continue;
|
|
36
|
+
// Skip correct async with usage
|
|
37
|
+
if (ASYNC_WITH_SESSION_RE.test(line))
|
|
38
|
+
continue;
|
|
39
|
+
const match = SESSION_ASSIGN_RE.exec(line);
|
|
40
|
+
if (!match)
|
|
41
|
+
continue;
|
|
42
|
+
const varName = match[2];
|
|
43
|
+
// Check if the specific variable has a close() call anywhere in the file
|
|
44
|
+
const varCloseRe = new RegExp(`\\bawait\\s+${varName}\\s*\\.\\s*close\\s*\\(\\)`);
|
|
45
|
+
const varHasClose = varCloseRe.test(normalizedText);
|
|
46
|
+
// If this variable is closed somewhere (shutdown hook) — it's a managed singleton, skip
|
|
47
|
+
if (varHasClose)
|
|
48
|
+
continue;
|
|
49
|
+
// If the file has generic await <x>.close() and this is likely a module-level singleton
|
|
50
|
+
// Heuristic: if the assignment is at module level (indent == 0) and file has await close → skip
|
|
51
|
+
const assignIndent = match[1].length;
|
|
52
|
+
if (assignIndent === 0 && fileHasAwaitClose)
|
|
53
|
+
continue;
|
|
54
|
+
violations.push({
|
|
55
|
+
ruleId: this.id,
|
|
56
|
+
ruleName: this.name,
|
|
57
|
+
policyRef: this.policyRef,
|
|
58
|
+
severity: this.severity,
|
|
59
|
+
filePath,
|
|
60
|
+
line: i + 1,
|
|
61
|
+
column: 1,
|
|
62
|
+
evidence: line.slice(0, 120),
|
|
63
|
+
operationalRisk: 'Each unclosed aiohttp session leaks a TCP connection pool and an underlying connector. ' +
|
|
64
|
+
'In services that create sessions per-request, this exhausts file descriptors within minutes under load.',
|
|
65
|
+
remediation: 'Use `async with aiohttp.ClientSession() as session:` for request-scoped sessions. ' +
|
|
66
|
+
'For long-lived sessions, create once at startup and call `await session.close()` in the app shutdown handler.',
|
|
67
|
+
determinism: 'heuristic-advisory',
|
|
68
|
+
confidence: 0.85,
|
|
69
|
+
language: 'python',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return violations;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.PY010LeakedAiohttpSession = PY010LeakedAiohttpSession;
|
|
80
|
+
//# sourceMappingURL=PY010-leaked-aiohttp-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY010-leaked-aiohttp-session.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY010-leaked-aiohttp-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
|
|
2
|
+
export declare class SR001SwallowedAsyncRejection implements StructuralRule {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
policyRef: string;
|
|
6
|
+
severity: "BLOCKING";
|
|
7
|
+
languages: RuleLanguage[];
|
|
8
|
+
description: string;
|
|
9
|
+
check(filePath: string, sourceText: string): StructuralViolation[];
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=SR001-swallowed-async-rejection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SR001-swallowed-async-rejection.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/rules/SR001-swallowed-async-rejection.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAiC7E,qBAAa,4BAA6B,YAAW,cAAc;IACjE,EAAE,SAAW;IACb,IAAI,SAA+B;IACnC,SAAS,SAAU;IACnB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAgC;IACzD,WAAW,SACgG;IAE3G,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAmFnE"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SR001SwallowedAsyncRejection = void 0;
|
|
37
|
+
const ts = __importStar(require("typescript"));
|
|
38
|
+
function containsThrowOrReject(node) {
|
|
39
|
+
if (ts.isThrowStatement(node))
|
|
40
|
+
return true;
|
|
41
|
+
if (ts.isCallExpression(node) &&
|
|
42
|
+
ts.isIdentifier(node.expression) &&
|
|
43
|
+
node.expression.text === 'reject') {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
let found = false;
|
|
47
|
+
ts.forEachChild(node, child => {
|
|
48
|
+
if (!found)
|
|
49
|
+
found = containsThrowOrReject(child);
|
|
50
|
+
});
|
|
51
|
+
return found;
|
|
52
|
+
}
|
|
53
|
+
function getLineAndCol(sf, pos) {
|
|
54
|
+
const lc = sf.getLineAndCharacterOfPosition(pos);
|
|
55
|
+
return { line: lc.line + 1, column: lc.character + 1 };
|
|
56
|
+
}
|
|
57
|
+
function getEvidenceLines(sourceText, line, extra = 1) {
|
|
58
|
+
const lines = sourceText.split('\n');
|
|
59
|
+
const start = line - 1;
|
|
60
|
+
const end = Math.min(start + extra, lines.length);
|
|
61
|
+
return lines
|
|
62
|
+
.slice(start, end)
|
|
63
|
+
.map(l => l.slice(0, 120))
|
|
64
|
+
.join('\n');
|
|
65
|
+
}
|
|
66
|
+
class SR001SwallowedAsyncRejection {
|
|
67
|
+
id = 'SR001';
|
|
68
|
+
name = 'Swallowed async rejection';
|
|
69
|
+
policyRef = 'P005';
|
|
70
|
+
severity = 'BLOCKING';
|
|
71
|
+
languages = ['typescript', 'javascript'];
|
|
72
|
+
description = '.catch() callbacks that do not contain a throw statement or a call to reject() silently absorb errors.';
|
|
73
|
+
check(filePath, sourceText) {
|
|
74
|
+
try {
|
|
75
|
+
const violations = [];
|
|
76
|
+
const ext = filePath.endsWith('.tsx')
|
|
77
|
+
? ts.ScriptKind.TSX
|
|
78
|
+
: filePath.endsWith('.jsx')
|
|
79
|
+
? ts.ScriptKind.JSX
|
|
80
|
+
: filePath.endsWith('.js')
|
|
81
|
+
? ts.ScriptKind.JS
|
|
82
|
+
: ts.ScriptKind.TS;
|
|
83
|
+
const sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, ext);
|
|
84
|
+
const visit = (node) => {
|
|
85
|
+
// Looking for: expr.catch(callback)
|
|
86
|
+
if (ts.isCallExpression(node) &&
|
|
87
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
88
|
+
node.expression.name.text === 'catch') {
|
|
89
|
+
const args = node.arguments;
|
|
90
|
+
if (args.length !== 1) {
|
|
91
|
+
ts.forEachChild(node, visit);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const callback = args[0];
|
|
95
|
+
// Exclude shorthand: .catch(console.error) or .catch(logger.error)
|
|
96
|
+
if (ts.isPropertyAccessExpression(callback) || ts.isIdentifier(callback)) {
|
|
97
|
+
ts.forEachChild(node, visit);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Must be arrow function or function expression
|
|
101
|
+
if (!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback)) {
|
|
102
|
+
ts.forEachChild(node, visit);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const body = callback.body;
|
|
106
|
+
// Exclude empty body: .catch(() => {})
|
|
107
|
+
if (ts.isBlock(body) && body.statements.length === 0) {
|
|
108
|
+
ts.forEachChild(node, visit);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// Check if body contains throw or reject
|
|
112
|
+
if (!containsThrowOrReject(body)) {
|
|
113
|
+
const { line, column } = getLineAndCol(sf, node.expression.name.getStart(sf));
|
|
114
|
+
const evidence = getEvidenceLines(sourceText, line, 2);
|
|
115
|
+
violations.push({
|
|
116
|
+
ruleId: this.id,
|
|
117
|
+
ruleName: this.name,
|
|
118
|
+
policyRef: this.policyRef,
|
|
119
|
+
severity: this.severity,
|
|
120
|
+
filePath,
|
|
121
|
+
line,
|
|
122
|
+
column,
|
|
123
|
+
evidence,
|
|
124
|
+
operationalRisk: 'Rejected promises are silently absorbed; errors never surface to callers or monitoring, ' +
|
|
125
|
+
'leading to invisible failures and stale state in production.',
|
|
126
|
+
remediation: 'Add `throw err` (or re-wrap: `throw new Error(err.message)`) inside the .catch() body, ' +
|
|
127
|
+
'or replace with `.catch(err => { logger.error(err); throw err; })`.',
|
|
128
|
+
determinism: 'deterministic-structural',
|
|
129
|
+
confidence: 0.92,
|
|
130
|
+
language: filePath.endsWith('.py') ? 'python' : filePath.match(/\.(js|jsx)$/) ? 'javascript' : 'typescript',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
ts.forEachChild(node, visit);
|
|
135
|
+
};
|
|
136
|
+
ts.forEachChild(sf, visit);
|
|
137
|
+
return violations;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.SR001SwallowedAsyncRejection = SR001SwallowedAsyncRejection;
|
|
145
|
+
//# sourceMappingURL=SR001-swallowed-async-rejection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SR001-swallowed-async-rejection.js","sourceRoot":"","sources":["../../../src/structural-rules/rules/SR001-swallowed-async-rejection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
|
|
2
|
+
export declare class SR002UnboundedCollection implements StructuralRule {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
policyRef: string;
|
|
6
|
+
severity: "BLOCKING";
|
|
7
|
+
languages: RuleLanguage[];
|
|
8
|
+
description: string;
|
|
9
|
+
check(filePath: string, sourceText: string): StructuralViolation[];
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=SR002-unbounded-collection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SR002-unbounded-collection.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/rules/SR002-unbounded-collection.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AA2E7E,qBAAa,wBAAyB,YAAW,cAAc;IAC7D,EAAE,SAAW;IACb,IAAI,SAA0B;IAC9B,SAAS,SAAU;IACnB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAgC;IACzD,WAAW,SAC+F;IAE1G,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAoGnE"}
|