@neurcode-ai/cli 0.9.63 → 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/control-plane.js +7 -7
- package/dist/commands/control-plane.js.map +1 -1
- package/dist/commands/fix.d.ts.map +1 -1
- package/dist/commands/fix.js +108 -1
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/patch-apply.d.ts +2 -0
- package/dist/commands/patch-apply.d.ts.map +1 -1
- package/dist/commands/patch-apply.js +331 -19
- package/dist/commands/patch-apply.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 +35 -5
- package/dist/commands/replay.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +336 -25
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/workspace.js +7 -7
- package/dist/commands/workspace.js.map +1 -1
- package/dist/daemon/server.d.ts +2 -2
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +2113 -32
- 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 +86 -4
- 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/matcher.d.ts.map +1 -1
- package/dist/intent-engine/matcher.js +2 -0
- package/dist/intent-engine/matcher.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/diff.d.ts +1 -1
- package/dist/patch-engine/diff.js +1 -1
- package/dist/patch-engine/generator.d.ts +9 -0
- package/dist/patch-engine/generator.d.ts.map +1 -1
- package/dist/patch-engine/generator.js +375 -17
- package/dist/patch-engine/generator.js.map +1 -1
- package/dist/patch-engine/index.d.ts +25 -25
- package/dist/patch-engine/index.d.ts.map +1 -1
- package/dist/patch-engine/index.js +134 -87
- package/dist/patch-engine/index.js.map +1 -1
- package/dist/patch-engine/patterns.d.ts +1 -1
- package/dist/patch-engine/patterns.d.ts.map +1 -1
- package/dist/patch-engine/patterns.js +282 -41
- package/dist/patch-engine/patterns.js.map +1 -1
- package/dist/patch-engine/rollback.d.ts +31 -0
- package/dist/patch-engine/rollback.d.ts.map +1 -0
- package/dist/patch-engine/rollback.js +275 -0
- package/dist/patch-engine/rollback.js.map +1 -0
- package/dist/patch-engine/safety.d.ts +28 -0
- package/dist/patch-engine/safety.d.ts.map +1 -0
- package/dist/patch-engine/safety.js +122 -0
- package/dist/patch-engine/safety.js.map +1 -0
- package/dist/patch-engine/transaction.d.ts +52 -0
- package/dist/patch-engine/transaction.d.ts.map +1 -0
- package/dist/patch-engine/transaction.js +93 -0
- package/dist/patch-engine/transaction.js.map +1 -0
- 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/advisory-signals.d.ts +5 -0
- package/dist/utils/advisory-signals.d.ts.map +1 -1
- package/dist/utils/advisory-signals.js +50 -12
- package/dist/utils/advisory-signals.js.map +1 -1
- package/dist/utils/ai-debt-budget.d.ts.map +1 -1
- package/dist/utils/ai-debt-budget.js +5 -2
- package/dist/utils/ai-debt-budget.js.map +1 -1
- 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/cli-json.d.ts.map +1 -1
- package/dist/utils/cli-json.js +80 -12
- package/dist/utils/cli-json.js.map +1 -1
- package/dist/utils/execution-bus.d.ts +10 -0
- package/dist/utils/execution-bus.d.ts.map +1 -1
- package/dist/utils/execution-bus.js +16 -0
- package/dist/utils/execution-bus.js.map +1 -1
- 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/policy-compiler.d.ts +6 -0
- package/dist/utils/policy-compiler.d.ts.map +1 -1
- package/dist/utils/policy-compiler.js +20 -0
- package/dist/utils/policy-compiler.js.map +1 -1
- 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 +10 -10
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PY004SwallowedAsyncException = void 0;
|
|
4
|
+
// Detect asyncio.gather( calls
|
|
5
|
+
const GATHER_RE = /asyncio\.gather\s*\(/g;
|
|
6
|
+
// return_exceptions=True present
|
|
7
|
+
const RETURN_EXCEPTIONS_RE = /return_exceptions\s*=\s*True/;
|
|
8
|
+
// Detect: await task inside try blocks where except only logs (no re-raise)
|
|
9
|
+
const AWAIT_IN_TRY_RE = /\bawait\s+\w+/;
|
|
10
|
+
const EXCEPT_RE = /^\s*except\b/;
|
|
11
|
+
const RERAISE_RE = /\braise\b/;
|
|
12
|
+
const LOGGING_RE = /\b(?:log|logger|logging|error|warn|report|track|capture|print)\s*[\.(]/i;
|
|
13
|
+
class PY004SwallowedAsyncException {
|
|
14
|
+
id = 'PY004';
|
|
15
|
+
name = 'Swallowed asyncio exception';
|
|
16
|
+
policyRef = 'P018';
|
|
17
|
+
severity = 'ADVISORY';
|
|
18
|
+
languages = ['python'];
|
|
19
|
+
description = 'asyncio.gather() without return_exceptions=True raises on first failure, silently cancelling other tasks. ' +
|
|
20
|
+
'Also detects try/await blocks where the except only logs without re-raising.';
|
|
21
|
+
check(filePath, sourceText) {
|
|
22
|
+
try {
|
|
23
|
+
const violations = [];
|
|
24
|
+
const lines = sourceText.split('\n');
|
|
25
|
+
// --- Pattern 1: asyncio.gather( without return_exceptions=True ---
|
|
26
|
+
for (let i = 0; i < lines.length; i++) {
|
|
27
|
+
const line = lines[i];
|
|
28
|
+
if (!line.includes('asyncio.gather('))
|
|
29
|
+
continue;
|
|
30
|
+
// Collect the full gather call (may span multiple lines)
|
|
31
|
+
let gatherCall = line;
|
|
32
|
+
let j = i + 1;
|
|
33
|
+
// Scan up to 10 lines ahead to find closing )
|
|
34
|
+
while (j < Math.min(i + 10, lines.length) && !gatherCall.includes(')')) {
|
|
35
|
+
gatherCall += '\n' + lines[j];
|
|
36
|
+
j++;
|
|
37
|
+
}
|
|
38
|
+
if (RETURN_EXCEPTIONS_RE.test(gatherCall))
|
|
39
|
+
continue;
|
|
40
|
+
// Check if it's wrapped in a try block (look backwards up to 5 lines)
|
|
41
|
+
let insideTry = false;
|
|
42
|
+
for (let k = Math.max(0, i - 5); k < i; k++) {
|
|
43
|
+
if (/^\s*try\s*:/.test(lines[k])) {
|
|
44
|
+
insideTry = true;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (insideTry)
|
|
49
|
+
continue;
|
|
50
|
+
const evidence = line.slice(0, 120);
|
|
51
|
+
violations.push({
|
|
52
|
+
ruleId: this.id,
|
|
53
|
+
ruleName: this.name,
|
|
54
|
+
policyRef: this.policyRef,
|
|
55
|
+
severity: this.severity,
|
|
56
|
+
filePath,
|
|
57
|
+
line: i + 1,
|
|
58
|
+
column: line.indexOf('asyncio.gather') + 1,
|
|
59
|
+
evidence,
|
|
60
|
+
operationalRisk: 'asyncio.gather() without return_exceptions=True raises on the first task failure, ' +
|
|
61
|
+
'but remaining tasks continue running as orphans. Their results and exceptions are lost, ' +
|
|
62
|
+
'and resources they hold remain locked.',
|
|
63
|
+
remediation: 'Use `results = await asyncio.gather(*tasks, return_exceptions=True)` then inspect ' +
|
|
64
|
+
'results: `errors = [r for r in results if isinstance(r, BaseException)]`.',
|
|
65
|
+
determinism: 'deterministic-structural',
|
|
66
|
+
confidence: 0.75,
|
|
67
|
+
language: 'python',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// --- Pattern 2: await inside try/except where except only logs, no re-raise ---
|
|
71
|
+
let i = 0;
|
|
72
|
+
while (i < lines.length) {
|
|
73
|
+
const line = lines[i];
|
|
74
|
+
const trimmed = line.trimStart();
|
|
75
|
+
if (!/^\s*try\s*:/.test(line)) {
|
|
76
|
+
i++;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const tryIndent = line.length - trimmed.length;
|
|
80
|
+
// Collect try body
|
|
81
|
+
const tryBody = [];
|
|
82
|
+
let j = i + 1;
|
|
83
|
+
while (j < lines.length) {
|
|
84
|
+
const bl = lines[j];
|
|
85
|
+
const bt = bl.trimStart();
|
|
86
|
+
if (bt.length === 0) {
|
|
87
|
+
j++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const bi = bl.length - bt.length;
|
|
91
|
+
if (bi <= tryIndent && bt.length > 0)
|
|
92
|
+
break;
|
|
93
|
+
tryBody.push(bl);
|
|
94
|
+
j++;
|
|
95
|
+
}
|
|
96
|
+
// Does the try body contain an await?
|
|
97
|
+
if (!AWAIT_IN_TRY_RE.test(tryBody.join('\n'))) {
|
|
98
|
+
i = j;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// Now look for except block
|
|
102
|
+
while (j < lines.length) {
|
|
103
|
+
const el = lines[j];
|
|
104
|
+
const et = el.trimStart();
|
|
105
|
+
if (et.length === 0) {
|
|
106
|
+
j++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const ei = el.length - et.length;
|
|
110
|
+
if (ei < tryIndent)
|
|
111
|
+
break;
|
|
112
|
+
if (!EXCEPT_RE.test(el)) {
|
|
113
|
+
j++;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
// Found an except — collect its body
|
|
117
|
+
const exceptBody = [];
|
|
118
|
+
let k = j + 1;
|
|
119
|
+
while (k < lines.length) {
|
|
120
|
+
const kb = lines[k];
|
|
121
|
+
const kt = kb.trimStart();
|
|
122
|
+
if (kt.length === 0) {
|
|
123
|
+
k++;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const ki = kb.length - kt.length;
|
|
127
|
+
if (ki <= ei && kt.length > 0)
|
|
128
|
+
break;
|
|
129
|
+
exceptBody.push(kb);
|
|
130
|
+
k++;
|
|
131
|
+
}
|
|
132
|
+
const exceptText = exceptBody.join('\n');
|
|
133
|
+
if (!RERAISE_RE.test(exceptText) && LOGGING_RE.test(exceptText)) {
|
|
134
|
+
// Only logs, no re-raise — flag it
|
|
135
|
+
const evidence = el.slice(0, 120);
|
|
136
|
+
violations.push({
|
|
137
|
+
ruleId: this.id,
|
|
138
|
+
ruleName: this.name,
|
|
139
|
+
policyRef: this.policyRef,
|
|
140
|
+
severity: this.severity,
|
|
141
|
+
filePath,
|
|
142
|
+
line: j + 1,
|
|
143
|
+
column: ei + 1,
|
|
144
|
+
evidence,
|
|
145
|
+
operationalRisk: 'Exception from an awaited task is caught, logged, but not re-raised. ' +
|
|
146
|
+
'The caller receives a successful return instead of an error, ' +
|
|
147
|
+
'causing silent data inconsistency.',
|
|
148
|
+
remediation: 'Re-raise after logging: `except Exception as e: logger.error(e); raise` ' +
|
|
149
|
+
'or let the exception propagate to a top-level handler.',
|
|
150
|
+
determinism: 'heuristic-advisory',
|
|
151
|
+
confidence: 0.75,
|
|
152
|
+
language: 'python',
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
j = k;
|
|
156
|
+
}
|
|
157
|
+
i = j;
|
|
158
|
+
}
|
|
159
|
+
return violations;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.PY004SwallowedAsyncException = PY004SwallowedAsyncException;
|
|
167
|
+
//# sourceMappingURL=PY004-swallowed-async-exception.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY004-swallowed-async-exception.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY004-swallowed-async-exception.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
|
|
2
|
+
export declare class PY005FastAPIWithoutPydantic 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=PY005-fastapi-without-pydantic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY005-fastapi-without-pydantic.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY005-fastapi-without-pydantic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AA0D7E,qBAAa,2BAA4B,YAAW,cAAc;IAChE,EAAE,SAAW;IACb,IAAI,SAAuE;IAC3E,SAAS,SAAU;IACnB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SACmH;IAE9H,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAiGnE"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PY005FastAPIWithoutPydantic = void 0;
|
|
4
|
+
// FastAPI route decorator patterns
|
|
5
|
+
const ROUTE_DECORATOR_RE = /^\s*@(?:app|router|api_router)\.(get|post|put|patch|delete|head|options|trace)\s*\(/;
|
|
6
|
+
// Detect request.body() or request.json() or await request.body() / await request.json()
|
|
7
|
+
const RAW_REQUEST_RE = /\b(?:request|req)\s*\.\s*(?:body|json)\s*\(\)/;
|
|
8
|
+
// Detect Pydantic model usage in function signature: param: ModelName
|
|
9
|
+
// where ModelName starts with uppercase and isn't Request/Response/BackgroundTasks
|
|
10
|
+
const PYDANTIC_PARAM_RE = /\w+\s*:\s*([A-Z][A-Za-z0-9]+)(?!\s*=\s*(?:None|True|False|\d))/;
|
|
11
|
+
// Common non-Pydantic FastAPI types to exclude
|
|
12
|
+
const NON_PYDANTIC_TYPES = new Set([
|
|
13
|
+
'Request',
|
|
14
|
+
'Response',
|
|
15
|
+
'BackgroundTasks',
|
|
16
|
+
'HTTPException',
|
|
17
|
+
'Depends',
|
|
18
|
+
'Optional',
|
|
19
|
+
'List',
|
|
20
|
+
'Dict',
|
|
21
|
+
'Any',
|
|
22
|
+
'str',
|
|
23
|
+
'int',
|
|
24
|
+
'float',
|
|
25
|
+
'bool',
|
|
26
|
+
'bytes',
|
|
27
|
+
]);
|
|
28
|
+
function extractFunctionSignature(lines, startIdx) {
|
|
29
|
+
// Collect function definition until the colon is found
|
|
30
|
+
let sig = '';
|
|
31
|
+
let depth = 0;
|
|
32
|
+
for (let i = startIdx; i < Math.min(startIdx + 10, lines.length); i++) {
|
|
33
|
+
sig += lines[i] + '\n';
|
|
34
|
+
for (const ch of lines[i]) {
|
|
35
|
+
if (ch === '(')
|
|
36
|
+
depth++;
|
|
37
|
+
else if (ch === ')')
|
|
38
|
+
depth--;
|
|
39
|
+
}
|
|
40
|
+
if (depth <= 0 && sig.includes('('))
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
return sig;
|
|
44
|
+
}
|
|
45
|
+
function hasPydanticModelParam(sig) {
|
|
46
|
+
// Find all type annotations in the signature
|
|
47
|
+
const paramRegex = /\w+\s*:\s*([A-Z][A-Za-z0-9_]*)/g;
|
|
48
|
+
let match;
|
|
49
|
+
while ((match = paramRegex.exec(sig)) !== null) {
|
|
50
|
+
const typeName = match[1];
|
|
51
|
+
if (!NON_PYDANTIC_TYPES.has(typeName)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
class PY005FastAPIWithoutPydantic {
|
|
58
|
+
id = 'PY005';
|
|
59
|
+
name = 'FastAPI route handler accessing raw request body without Pydantic';
|
|
60
|
+
policyRef = 'P019';
|
|
61
|
+
severity = 'BLOCKING';
|
|
62
|
+
languages = ['python'];
|
|
63
|
+
description = 'FastAPI route handlers that access request.body() or request.json() without a Pydantic model parameter bypass validation.';
|
|
64
|
+
check(filePath, sourceText) {
|
|
65
|
+
try {
|
|
66
|
+
const violations = [];
|
|
67
|
+
const lines = sourceText.split('\n');
|
|
68
|
+
let i = 0;
|
|
69
|
+
while (i < lines.length) {
|
|
70
|
+
const line = lines[i];
|
|
71
|
+
// Look for route decorator
|
|
72
|
+
if (!ROUTE_DECORATOR_RE.test(line)) {
|
|
73
|
+
i++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const decoratorLine = i;
|
|
77
|
+
// Find the function definition (next def after decorator, skip other decorators)
|
|
78
|
+
let funcDefLine = -1;
|
|
79
|
+
let j = i + 1;
|
|
80
|
+
while (j < Math.min(i + 10, lines.length)) {
|
|
81
|
+
const l = lines[j].trimStart();
|
|
82
|
+
if (/^(?:async\s+)?def\s+\w+\s*\(/.test(l)) {
|
|
83
|
+
funcDefLine = j;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
if (l.length > 0 && !l.startsWith('@') && !l.startsWith('#'))
|
|
87
|
+
break;
|
|
88
|
+
j++;
|
|
89
|
+
}
|
|
90
|
+
if (funcDefLine === -1) {
|
|
91
|
+
i = j + 1;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
// Extract function signature
|
|
95
|
+
const sig = extractFunctionSignature(lines, funcDefLine);
|
|
96
|
+
// If signature has a Pydantic model param, no violation
|
|
97
|
+
if (hasPydanticModelParam(sig)) {
|
|
98
|
+
i = funcDefLine + 1;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// Find function body (collect until next function/class at same or lower indent)
|
|
102
|
+
const funcIndent = lines[funcDefLine].length - lines[funcDefLine].trimStart().length;
|
|
103
|
+
const bodyLines = [];
|
|
104
|
+
let k = funcDefLine + 1;
|
|
105
|
+
while (k < lines.length) {
|
|
106
|
+
const bl = lines[k];
|
|
107
|
+
const bt = bl.trimStart();
|
|
108
|
+
if (bt.length === 0) {
|
|
109
|
+
k++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const bi = bl.length - bt.length;
|
|
113
|
+
if (bi <= funcIndent && bt.length > 0)
|
|
114
|
+
break;
|
|
115
|
+
bodyLines.push(bl);
|
|
116
|
+
k++;
|
|
117
|
+
}
|
|
118
|
+
const bodyText = bodyLines.join('\n');
|
|
119
|
+
// Check if body accesses request.body() or request.json()
|
|
120
|
+
if (!RAW_REQUEST_RE.test(bodyText)) {
|
|
121
|
+
i = k;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const evidence = lines[funcDefLine].slice(0, 120);
|
|
125
|
+
violations.push({
|
|
126
|
+
ruleId: this.id,
|
|
127
|
+
ruleName: this.name,
|
|
128
|
+
policyRef: this.policyRef,
|
|
129
|
+
severity: this.severity,
|
|
130
|
+
filePath,
|
|
131
|
+
line: funcDefLine + 1,
|
|
132
|
+
column: (lines[funcDefLine].match(/^\s*/)?.[0].length ?? 0) + 1,
|
|
133
|
+
evidence,
|
|
134
|
+
operationalRisk: 'Raw request body is accessed without Pydantic validation. ' +
|
|
135
|
+
'Malformed, oversized, or malicious payloads reach business logic directly, ' +
|
|
136
|
+
'causing runtime errors, type confusion, or injection vulnerabilities.',
|
|
137
|
+
remediation: 'Add a Pydantic model parameter to the route handler: ' +
|
|
138
|
+
'`async def handler(data: MyRequestModel)` and let FastAPI validate and parse the body automatically. ' +
|
|
139
|
+
'Remove the manual `request.body()` / `request.json()` call.',
|
|
140
|
+
determinism: 'deterministic-structural',
|
|
141
|
+
confidence: 0.80,
|
|
142
|
+
language: 'python',
|
|
143
|
+
});
|
|
144
|
+
i = k;
|
|
145
|
+
}
|
|
146
|
+
return violations;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
exports.PY005FastAPIWithoutPydantic = PY005FastAPIWithoutPydantic;
|
|
154
|
+
//# sourceMappingURL=PY005-fastapi-without-pydantic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY005-fastapi-without-pydantic.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY005-fastapi-without-pydantic.ts"],"names":[],"mappings":";;;AAEA,mCAAmC;AACnC,MAAM,kBAAkB,GAAG,qFAAqF,CAAC;AAEjH,yFAAyF;AACzF,MAAM,cAAc,GAAG,+CAA+C,CAAC;AAEvE,sEAAsE;AACtE,mFAAmF;AACnF,MAAM,iBAAiB,GAAG,gEAAgE,CAAC;AAE3F,+CAA+C;AAC/C,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,SAAS;IACT,UAAU;IACV,iBAAiB;IACjB,eAAe;IACf,SAAS;IACT,UAAU;IACV,MAAM;IACN,MAAM;IACN,KAAK;IACL,KAAK;IACL,KAAK;IACL,OAAO;IACP,MAAM;IACN,OAAO;CACR,CAAC,CAAC;AAEH,SAAS,wBAAwB,CAAC,KAAe,EAAE,QAAgB;IACjE,uDAAuD;IACvD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACtE,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QACvB,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1B,IAAI,EAAE,KAAK,GAAG;gBAAE,KAAK,EAAE,CAAC;iBACnB,IAAI,EAAE,KAAK,GAAG;gBAAE,KAAK,EAAE,CAAC;QAC/B,CAAC;QACD,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,MAAM;IAC7C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,qBAAqB,CAAC,GAAW;IACxC,6CAA6C;IAC7C,MAAM,UAAU,GAAG,iCAAiC,CAAC;IACrD,IAAI,KAAK,CAAC;IACV,OAAO,CAAC,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC/C,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAa,2BAA2B;IACtC,EAAE,GAAG,OAAO,CAAC;IACb,IAAI,GAAG,mEAAmE,CAAC;IAC3E,SAAS,GAAG,MAAM,CAAC;IACnB,QAAQ,GAAG,UAAmB,CAAC;IAC/B,SAAS,GAAmB,CAAC,QAAQ,CAAC,CAAC;IACvC,WAAW,GACT,2HAA2H,CAAC;IAE9H,KAAK,CAAC,QAAgB,EAAE,UAAkB;QACxC,IAAI,CAAC;YACH,MAAM,UAAU,GAA0B,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAErC,IAAI,CAAC,GAAG,CAAC,CAAC;YACV,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;gBACxB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBAEtB,2BAA2B;gBAC3B,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACnC,CAAC,EAAE,CAAC;oBACJ,SAAS;gBACX,CAAC;gBAED,MAAM,aAAa,GAAG,CAAC,CAAC;gBAExB,iFAAiF;gBACjF,IAAI,WAAW,GAAG,CAAC,CAAC,CAAC;gBACrB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACd,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC1C,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;oBAC/B,IAAI,8BAA8B,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;wBAC3C,WAAW,GAAG,CAAC,CAAC;wBAChB,MAAM;oBACR,CAAC;oBACD,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;wBAAE,MAAM;oBACpE,CAAC,EAAE,CAAC;gBACN,CAAC;gBAED,IAAI,WAAW,KAAK,CAAC,CAAC,EAAE,CAAC;oBACvB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;oBACV,SAAS;gBACX,CAAC;gBAED,6BAA6B;gBAC7B,MAAM,GAAG,GAAG,wBAAwB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;gBAEzD,wDAAwD;gBACxD,IAAI,qBAAqB,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC/B,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC;oBACpB,SAAS;gBACX,CAAC;gBAED,iFAAiF;gBACjF,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC;gBACrF,MAAM,SAAS,GAAa,EAAE,CAAC;gBAC/B,IAAI,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC;gBACxB,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;oBACxB,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBACpB,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC;oBAC1B,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBAAC,CAAC,EAAE,CAAC;wBAAC,SAAS;oBAAC,CAAC;oBACvC,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC;oBACjC,IAAI,EAAE,IAAI,UAAU,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;wBAAE,MAAM;oBAC7C,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBACnB,CAAC,EAAE,CAAC;gBACN,CAAC;gBAED,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAEtC,0DAA0D;gBAC1D,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACnC,CAAC,GAAG,CAAC,CAAC;oBACN,SAAS;gBACX,CAAC;gBAED,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;gBAClD,UAAU,CAAC,IAAI,CAAC;oBACd,MAAM,EAAE,IAAI,CAAC,EAAE;oBACf,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,QAAQ;oBACR,IAAI,EAAE,WAAW,GAAG,CAAC;oBACrB,MAAM,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC;oBAC/D,QAAQ;oBACR,eAAe,EACb,4DAA4D;wBAC5D,6EAA6E;wBAC7E,uEAAuE;oBACzE,WAAW,EACT,uDAAuD;wBACvD,uGAAuG;wBACvG,6DAA6D;oBAC/D,WAAW,EAAE,0BAA0B;oBACvC,UAAU,EAAE,IAAI;oBAChB,QAAQ,EAAE,QAAQ;iBACnB,CAAC,CAAC;gBAEH,CAAC,GAAG,CAAC,CAAC;YACR,CAAC;YAED,OAAO,UAAU,CAAC;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF;AA1GD,kEA0GC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
|
|
2
|
+
export declare class PY006BlockingIOInAsync 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=PY006-blocking-io-in-async.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY006-blocking-io-in-async.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY006-blocking-io-in-async.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAwB7E,qBAAa,sBAAuB,YAAW,cAAc;IAC3D,EAAE,SAAW;IACb,IAAI,SAAwC;IAC5C,SAAS,SAAW;IACpB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SACoG;IAE/G,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CA8GnE"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PY006BlockingIOInAsync = void 0;
|
|
4
|
+
// Matches `async def funcname(`
|
|
5
|
+
const ASYNC_DEF_RE = /^(\s*)async\s+def\s+\w+\s*\(/;
|
|
6
|
+
// Matches nested sync `def` (not async def)
|
|
7
|
+
const SYNC_DEF_RE = /^\s+def\s+\w+\s*\(/;
|
|
8
|
+
// Blocking patterns to detect inside async function bodies
|
|
9
|
+
const BLOCKING_PATTERNS = [
|
|
10
|
+
{ re: /\btime\.sleep\s*\(/, label: 'time.sleep()' },
|
|
11
|
+
{ re: /\brequests\.get\s*\(/, label: 'requests.get()' },
|
|
12
|
+
{ re: /\brequests\.post\s*\(/, label: 'requests.post()' },
|
|
13
|
+
{ re: /\brequests\.request\s*\(/, label: 'requests.request()' },
|
|
14
|
+
{ re: /\bsubprocess\.run\s*\(/, label: 'subprocess.run()' },
|
|
15
|
+
{ re: /\bsubprocess\.call\s*\(/, label: 'subprocess.call()' },
|
|
16
|
+
// open( not preceded by aiofiles.open or async with
|
|
17
|
+
{ re: /(?<!aiofiles\.)(?<!\bwith\s)\bopen\s*\(/, label: 'open()' },
|
|
18
|
+
];
|
|
19
|
+
function getIndent(line) {
|
|
20
|
+
return line.length - line.trimStart().length;
|
|
21
|
+
}
|
|
22
|
+
class PY006BlockingIOInAsync {
|
|
23
|
+
id = 'PY006';
|
|
24
|
+
name = 'Blocking I/O call inside async def';
|
|
25
|
+
policyRef = 'PY006';
|
|
26
|
+
severity = 'BLOCKING';
|
|
27
|
+
languages = ['python'];
|
|
28
|
+
description = 'Blocking I/O (time.sleep, requests, open, subprocess) inside an async def function freezes the event loop.';
|
|
29
|
+
check(filePath, sourceText) {
|
|
30
|
+
try {
|
|
31
|
+
const violations = [];
|
|
32
|
+
// Normalize line endings
|
|
33
|
+
const lines = sourceText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
|
|
34
|
+
// Does the file import aiofiles?
|
|
35
|
+
const importsAiofiles = /\baiofiles\b/.test(sourceText);
|
|
36
|
+
let i = 0;
|
|
37
|
+
while (i < lines.length) {
|
|
38
|
+
const line = lines[i];
|
|
39
|
+
const asyncMatch = ASYNC_DEF_RE.exec(line);
|
|
40
|
+
if (!asyncMatch) {
|
|
41
|
+
i++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const funcIndent = asyncMatch[1].length;
|
|
45
|
+
const bodyStart = i + 1;
|
|
46
|
+
i++;
|
|
47
|
+
// Collect the function body: lines with indent > funcIndent
|
|
48
|
+
while (i < lines.length) {
|
|
49
|
+
const bl = lines[i];
|
|
50
|
+
const trimmed = bl.trimStart();
|
|
51
|
+
// Blank lines are part of the body
|
|
52
|
+
if (trimmed.length === 0) {
|
|
53
|
+
i++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const lineIndent = getIndent(bl);
|
|
57
|
+
// If we're back at or before the function's indent, we've left the body
|
|
58
|
+
if (lineIndent <= funcIndent)
|
|
59
|
+
break;
|
|
60
|
+
// Skip comment lines
|
|
61
|
+
if (trimmed.startsWith('#')) {
|
|
62
|
+
i++;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Skip lines with noqa
|
|
66
|
+
if (/\bnoqa\b/.test(bl)) {
|
|
67
|
+
i++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
// Skip lines that are inside a nested sync def
|
|
71
|
+
// (we only care about the top-level async body, not nested sync helpers)
|
|
72
|
+
if (SYNC_DEF_RE.test(bl)) {
|
|
73
|
+
// Skip the entire nested sync function body
|
|
74
|
+
const nestedIndent = lineIndent;
|
|
75
|
+
i++;
|
|
76
|
+
while (i < lines.length) {
|
|
77
|
+
const nb = lines[i];
|
|
78
|
+
const nt = nb.trimStart();
|
|
79
|
+
if (nt.length === 0) {
|
|
80
|
+
i++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (getIndent(nb) <= nestedIndent)
|
|
84
|
+
break;
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
// Check blocking patterns
|
|
90
|
+
for (const { re, label } of BLOCKING_PATTERNS) {
|
|
91
|
+
// If it's an open() hit and aiofiles is imported, skip
|
|
92
|
+
if (label === 'open()' && importsAiofiles)
|
|
93
|
+
continue;
|
|
94
|
+
if (re.test(bl)) {
|
|
95
|
+
violations.push({
|
|
96
|
+
ruleId: this.id,
|
|
97
|
+
ruleName: this.name,
|
|
98
|
+
policyRef: this.policyRef,
|
|
99
|
+
severity: this.severity,
|
|
100
|
+
filePath,
|
|
101
|
+
line: i + 1,
|
|
102
|
+
column: 1,
|
|
103
|
+
evidence: bl.slice(0, 120),
|
|
104
|
+
operationalRisk: `\`${label}\` inside an async function blocks the entire event loop thread. ` +
|
|
105
|
+
'All other coroutines are frozen for the duration of the call. ' +
|
|
106
|
+
'Under load, a single blocking call can cause 100ms+ latency spikes across all concurrent requests.',
|
|
107
|
+
remediation: 'Replace time.sleep(n) with `await asyncio.sleep(n)`, ' +
|
|
108
|
+
'requests.get() with `await aiohttp.ClientSession().get()`, ' +
|
|
109
|
+
'open() with `async with aiofiles.open()`, ' +
|
|
110
|
+
'subprocess.run() with `await asyncio.create_subprocess_exec()`.',
|
|
111
|
+
determinism: 'heuristic-advisory',
|
|
112
|
+
confidence: 0.82,
|
|
113
|
+
language: 'python',
|
|
114
|
+
});
|
|
115
|
+
break; // one violation per line
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
i++;
|
|
119
|
+
}
|
|
120
|
+
void bodyStart; // suppress unused warning
|
|
121
|
+
}
|
|
122
|
+
return violations;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.PY006BlockingIOInAsync = PY006BlockingIOInAsync;
|
|
130
|
+
//# sourceMappingURL=PY006-blocking-io-in-async.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY006-blocking-io-in-async.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY006-blocking-io-in-async.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
|
|
2
|
+
export declare class PY007SQLAlchemySessionLeak 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=PY007-sqlalchemy-session-leak.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY007-sqlalchemy-session-leak.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY007-sqlalchemy-session-leak.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAmB7E,qBAAa,0BAA2B,YAAW,cAAc;IAC/D,EAAE,SAAW;IACb,IAAI,SAAwD;IAC5D,SAAS,SAAW;IACpB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SACwG;IAEnH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CA6EnE"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PY007SQLAlchemySessionLeak = void 0;
|
|
4
|
+
// Matches bare session assignment: session = Session() / session = SessionLocal() etc.
|
|
5
|
+
// Captures the variable name and the constructor call
|
|
6
|
+
const SESSION_ASSIGN_RE = /^(\s*)(\w+)\s*=\s*(Session|SessionLocal|AsyncSession|get_session|ScopedSession|sessionmaker\(\))\s*\(/;
|
|
7
|
+
// Matches a `with` or `async with` block opening with Session
|
|
8
|
+
const WITH_SESSION_RE = /^\s*(?:async\s+)?with\s+.*Session/;
|
|
9
|
+
// Matches session.close() in a finally block vicinity
|
|
10
|
+
const SESSION_CLOSE_RE = /\bsession\s*\.\s*close\s*\(\)/;
|
|
11
|
+
// Matches a `finally:` block
|
|
12
|
+
const FINALLY_RE = /^\s*finally\s*:/;
|
|
13
|
+
function getIndent(line) {
|
|
14
|
+
return line.length - line.trimStart().length;
|
|
15
|
+
}
|
|
16
|
+
class PY007SQLAlchemySessionLeak {
|
|
17
|
+
id = 'PY007';
|
|
18
|
+
name = 'SQLAlchemy session created outside context manager';
|
|
19
|
+
policyRef = 'PY007';
|
|
20
|
+
severity = 'BLOCKING';
|
|
21
|
+
languages = ['python'];
|
|
22
|
+
description = 'SQLAlchemy session assigned without a context manager or try/finally close() risks connection pool exhaustion.';
|
|
23
|
+
check(filePath, sourceText) {
|
|
24
|
+
try {
|
|
25
|
+
const violations = [];
|
|
26
|
+
// Normalize line endings
|
|
27
|
+
const lines = sourceText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
const line = lines[i];
|
|
30
|
+
// Skip if this line is a `with Session()` — safe usage
|
|
31
|
+
if (WITH_SESSION_RE.test(line))
|
|
32
|
+
continue;
|
|
33
|
+
const match = SESSION_ASSIGN_RE.exec(line);
|
|
34
|
+
if (!match)
|
|
35
|
+
continue;
|
|
36
|
+
const varName = match[2];
|
|
37
|
+
const assignIndent = match[1].length;
|
|
38
|
+
// Look ahead: find if there is a try/finally with session.close()
|
|
39
|
+
// Search up to 60 lines ahead within the same or deeper indentation scope
|
|
40
|
+
let hasFinallyClose = false;
|
|
41
|
+
let inFinally = false;
|
|
42
|
+
for (let j = i + 1; j < Math.min(i + 60, lines.length); j++) {
|
|
43
|
+
const jl = lines[j];
|
|
44
|
+
const jt = jl.trimStart();
|
|
45
|
+
if (jt.length === 0)
|
|
46
|
+
continue;
|
|
47
|
+
const jIndent = getIndent(jl);
|
|
48
|
+
// If we've gone back to a shallower indent than the assignment, stop
|
|
49
|
+
if (jIndent < assignIndent)
|
|
50
|
+
break;
|
|
51
|
+
if (FINALLY_RE.test(jl)) {
|
|
52
|
+
inFinally = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (inFinally) {
|
|
56
|
+
// Check for varName.close() or generic session.close()
|
|
57
|
+
const closeRe = new RegExp(`\\b${varName}\\s*\\.\\s*close\\s*\\(\\)`);
|
|
58
|
+
if (closeRe.test(jl) || SESSION_CLOSE_RE.test(jl)) {
|
|
59
|
+
hasFinallyClose = true;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!hasFinallyClose) {
|
|
65
|
+
violations.push({
|
|
66
|
+
ruleId: this.id,
|
|
67
|
+
ruleName: this.name,
|
|
68
|
+
policyRef: this.policyRef,
|
|
69
|
+
severity: this.severity,
|
|
70
|
+
filePath,
|
|
71
|
+
line: i + 1,
|
|
72
|
+
column: 1,
|
|
73
|
+
evidence: line.slice(0, 120),
|
|
74
|
+
operationalRisk: 'Unclosed SQLAlchemy sessions hold database connections open indefinitely. ' +
|
|
75
|
+
'Connection pools exhaust under load, causing `TimeoutError: QueuePool limit of size X overflow Y reached` ' +
|
|
76
|
+
'in production within hours of deployment.',
|
|
77
|
+
remediation: 'Use `with Session() as session:` or `async with AsyncSession() as session:` for automatic cleanup. ' +
|
|
78
|
+
'Never use bare `session = Session()` without a corresponding `finally: session.close()`.',
|
|
79
|
+
determinism: 'heuristic-advisory',
|
|
80
|
+
confidence: 0.78,
|
|
81
|
+
language: 'python',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return violations;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.PY007SQLAlchemySessionLeak = PY007SQLAlchemySessionLeak;
|
|
93
|
+
//# sourceMappingURL=PY007-sqlalchemy-session-leak.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY007-sqlalchemy-session-leak.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY007-sqlalchemy-session-leak.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
|
|
2
|
+
export declare class PY008CeleryTaskWithoutRetry implements StructuralRule {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
policyRef: string;
|
|
6
|
+
severity: "ADVISORY";
|
|
7
|
+
languages: RuleLanguage[];
|
|
8
|
+
description: string;
|
|
9
|
+
check(filePath: string, sourceText: string): StructuralViolation[];
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=PY008-celery-task-without-retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY008-celery-task-without-retry.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY008-celery-task-without-retry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AA8C7E,qBAAa,2BAA4B,YAAW,cAAc;IAChE,EAAE,SAAW;IACb,IAAI,SAA6C;IACjD,SAAS,SAAW;IACpB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SACmH;IAE9H,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAiHnE"}
|