@neurcode-ai/cli 0.9.64 → 0.9.66
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/bootstrap-policy.d.ts +29 -0
- package/dist/commands/bootstrap-policy.d.ts.map +1 -0
- package/dist/commands/bootstrap-policy.js +334 -0
- package/dist/commands/bootstrap-policy.js.map +1 -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/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +82 -0
- package/dist/commands/doctor.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/quickstart.d.ts +21 -0
- package/dist/commands/quickstart.d.ts.map +1 -0
- package/dist/commands/quickstart.js +178 -0
- package/dist/commands/quickstart.js.map +1 -0
- package/dist/commands/remediate-export.d.ts +31 -0
- package/dist/commands/remediate-export.d.ts.map +1 -0
- package/dist/commands/remediate-export.js +283 -0
- package/dist/commands/remediate-export.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 +409 -30
- 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-invariants.d.ts +88 -0
- package/dist/governance/canonical-invariants.d.ts.map +1 -0
- package/dist/governance/canonical-invariants.js +197 -0
- package/dist/governance/canonical-invariants.js.map +1 -0
- package/dist/governance/canonical-ordering.d.ts +76 -0
- package/dist/governance/canonical-ordering.d.ts.map +1 -0
- package/dist/governance/canonical-ordering.js +189 -0
- package/dist/governance/canonical-ordering.js.map +1 -0
- package/dist/governance/canonical-pipeline.d.ts +45 -0
- package/dist/governance/canonical-pipeline.d.ts.map +1 -0
- package/dist/governance/canonical-pipeline.js +616 -0
- package/dist/governance/canonical-pipeline.js.map +1 -0
- package/dist/governance/diff-line-provenance.d.ts +59 -0
- package/dist/governance/diff-line-provenance.d.ts.map +1 -0
- package/dist/governance/diff-line-provenance.js +118 -0
- package/dist/governance/diff-line-provenance.js.map +1 -0
- package/dist/governance/pilot-readiness.d.ts +34 -0
- package/dist/governance/pilot-readiness.d.ts.map +1 -0
- package/dist/governance/pilot-readiness.js +226 -0
- package/dist/governance/pilot-readiness.js.map +1 -0
- package/dist/governance/policy-parity-validator.d.ts +62 -0
- package/dist/governance/policy-parity-validator.d.ts.map +1 -0
- package/dist/governance/policy-parity-validator.js +137 -0
- package/dist/governance/policy-parity-validator.js.map +1 -0
- package/dist/governance/remediation-boundary.d.ts +55 -0
- package/dist/governance/remediation-boundary.d.ts.map +1 -0
- package/dist/governance/remediation-boundary.js +120 -0
- package/dist/governance/remediation-boundary.js.map +1 -0
- package/dist/governance/structural-cache.d.ts +103 -0
- package/dist/governance/structural-cache.d.ts.map +1 -0
- package/dist/governance/structural-cache.js +240 -0
- package/dist/governance/structural-cache.js.map +1 -0
- package/dist/governance/structural-on-diff.d.ts +33 -0
- package/dist/governance/structural-on-diff.d.ts.map +1 -0
- package/dist/governance/structural-on-diff.js +67 -0
- package/dist/governance/structural-on-diff.js.map +1 -0
- package/dist/governance/structural-policy-merge.d.ts +22 -0
- package/dist/governance/structural-policy-merge.d.ts.map +1 -0
- package/dist/governance/structural-policy-merge.js +32 -0
- package/dist/governance/structural-policy-merge.js.map +1 -0
- package/dist/governance/verify-runtime-guard.d.ts +99 -0
- package/dist/governance/verify-runtime-guard.d.ts.map +1 -0
- package/dist/governance/verify-runtime-guard.js +129 -0
- package/dist/governance/verify-runtime-guard.js.map +1 -0
- package/dist/index.js +107 -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/repo-classifier.d.ts +64 -0
- package/dist/intent-engine/repo-classifier.d.ts.map +1 -0
- package/dist/intent-engine/repo-classifier.js +178 -0
- package/dist/intent-engine/repo-classifier.js.map +1 -0
- 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 +45 -0
- package/dist/structural-rules/index.d.ts.map +1 -0
- package/dist/structural-rules/index.js +158 -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 +32 -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 +277 -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/python/PY011-thread-lifecycle.d.ts +11 -0
- package/dist/structural-rules/python/PY011-thread-lifecycle.d.ts.map +1 -0
- package/dist/structural-rules/python/PY011-thread-lifecycle.js +97 -0
- package/dist/structural-rules/python/PY011-thread-lifecycle.js.map +1 -0
- package/dist/structural-rules/python/PY012-asyncio-run-misuse.d.ts +11 -0
- package/dist/structural-rules/python/PY012-asyncio-run-misuse.d.ts.map +1 -0
- package/dist/structural-rules/python/PY012-asyncio-run-misuse.js +83 -0
- package/dist/structural-rules/python/PY012-asyncio-run-misuse.js.map +1 -0
- package/dist/structural-rules/python/PY013-mutable-default-arg.d.ts +11 -0
- package/dist/structural-rules/python/PY013-mutable-default-arg.d.ts.map +1 -0
- package/dist/structural-rules/python/PY013-mutable-default-arg.js +73 -0
- package/dist/structural-rules/python/PY013-mutable-default-arg.js.map +1 -0
- package/dist/structural-rules/python/PY014-fixed-sleep-retry.d.ts +11 -0
- package/dist/structural-rules/python/PY014-fixed-sleep-retry.d.ts.map +1 -0
- package/dist/structural-rules/python/PY014-fixed-sleep-retry.js +115 -0
- package/dist/structural-rules/python/PY014-fixed-sleep-retry.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 +55 -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/utils/verify-runtime-stability.d.ts +142 -0
- package/dist/utils/verify-runtime-stability.d.ts.map +1 -0
- package/dist/utils/verify-runtime-stability.js +230 -0
- package/dist/utils/verify-runtime-stability.js.map +1 -0
- 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,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PY003BroadExceptClause = void 0;
|
|
4
|
+
exports.stripCommentsAndStrings = stripCommentsAndStrings;
|
|
5
|
+
exports.classifyExceptionFlow = classifyExceptionFlow;
|
|
6
|
+
// Matches: except Exception: or except Exception as e:
|
|
7
|
+
const BROAD_EXCEPT_RE = /^(\s*)except\s+Exception(\s+as\s+\w+)?\s*:/;
|
|
8
|
+
// Logging/reporting call patterns — only checked AFTER stripping comments
|
|
9
|
+
const LOGGING_RE = /\b(?:log|logger|logging|error|warn|warning|report|track|capture|sentry|bugsnag|rollbar|print)\s*[\.(]/i;
|
|
10
|
+
/**
|
|
11
|
+
* Strip Python comment lines and string literal regions from source lines.
|
|
12
|
+
*
|
|
13
|
+
* Algorithm (deterministic state machine):
|
|
14
|
+
* - Track whether we are inside a triple-quoted string (""" or ''')
|
|
15
|
+
* - Track whether we are inside a single-quoted string (" or ')
|
|
16
|
+
* - If a line starts with # (after stripping indent) → replace with empty string
|
|
17
|
+
* - Content inside string regions is neutralized (replaced with spaces of same length)
|
|
18
|
+
*
|
|
19
|
+
* This is NOT a full Python tokenizer — it handles the common cases that enable
|
|
20
|
+
* bypass of governance checks while remaining O(n) and dependency-free.
|
|
21
|
+
*/
|
|
22
|
+
function stripCommentsAndStrings(lines) {
|
|
23
|
+
const result = [];
|
|
24
|
+
let inTripleDouble = false; // inside """..."""
|
|
25
|
+
let inTripleSingle = false; // inside '''...'''
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
const trimmed = line.trimStart();
|
|
28
|
+
// If we are inside a triple-quoted block, look for the closing delimiter
|
|
29
|
+
if (inTripleDouble) {
|
|
30
|
+
const closeIdx = line.indexOf('"""');
|
|
31
|
+
if (closeIdx !== -1) {
|
|
32
|
+
inTripleDouble = false;
|
|
33
|
+
// Neutralize up to and including the closing delimiter
|
|
34
|
+
result.push(' '.repeat(line.length));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
result.push(' '.repeat(line.length));
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (inTripleSingle) {
|
|
42
|
+
const closeIdx = line.indexOf("'''");
|
|
43
|
+
if (closeIdx !== -1) {
|
|
44
|
+
inTripleSingle = false;
|
|
45
|
+
result.push(' '.repeat(line.length));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
result.push(' '.repeat(line.length));
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
// Full-line comment — blank it entirely
|
|
53
|
+
if (trimmed.startsWith('#')) {
|
|
54
|
+
result.push('');
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// Scan for string/comment delimiters character by character
|
|
58
|
+
let out = '';
|
|
59
|
+
let i = 0;
|
|
60
|
+
let inSingleQ = false;
|
|
61
|
+
let inDoubleQ = false;
|
|
62
|
+
while (i < line.length) {
|
|
63
|
+
const ch = line[i];
|
|
64
|
+
const remaining = line.slice(i);
|
|
65
|
+
if (!inSingleQ && !inDoubleQ) {
|
|
66
|
+
// Check for triple-quote opening
|
|
67
|
+
if (remaining.startsWith('"""')) {
|
|
68
|
+
const rest = line.slice(i + 3);
|
|
69
|
+
const closeInSameLine = rest.indexOf('"""');
|
|
70
|
+
if (closeInSameLine !== -1) {
|
|
71
|
+
// Triple-quote opens and closes on same line — neutralize it
|
|
72
|
+
out += ' '.repeat(3 + closeInSameLine + 3);
|
|
73
|
+
i += 3 + closeInSameLine + 3;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
inTripleDouble = true;
|
|
78
|
+
// Neutralize rest of line
|
|
79
|
+
out += ' '.repeat(line.length - i);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (remaining.startsWith("'''")) {
|
|
84
|
+
const rest = line.slice(i + 3);
|
|
85
|
+
const closeInSameLine = rest.indexOf("'''");
|
|
86
|
+
if (closeInSameLine !== -1) {
|
|
87
|
+
out += ' '.repeat(3 + closeInSameLine + 3);
|
|
88
|
+
i += 3 + closeInSameLine + 3;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
inTripleSingle = true;
|
|
93
|
+
out += ' '.repeat(line.length - i);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Start of single-line string
|
|
98
|
+
if (ch === '"') {
|
|
99
|
+
inDoubleQ = true;
|
|
100
|
+
out += ' ';
|
|
101
|
+
i++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (ch === "'") {
|
|
105
|
+
inSingleQ = true;
|
|
106
|
+
out += ' ';
|
|
107
|
+
i++;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// Inline comment
|
|
111
|
+
if (ch === '#') {
|
|
112
|
+
// Rest of line is comment — stop
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
out += ch;
|
|
116
|
+
}
|
|
117
|
+
else if (inDoubleQ) {
|
|
118
|
+
if (ch === '\\') {
|
|
119
|
+
out += ' ';
|
|
120
|
+
i += 2;
|
|
121
|
+
continue;
|
|
122
|
+
} // escape
|
|
123
|
+
if (ch === '"') {
|
|
124
|
+
inDoubleQ = false;
|
|
125
|
+
}
|
|
126
|
+
out += ' ';
|
|
127
|
+
}
|
|
128
|
+
else if (inSingleQ) {
|
|
129
|
+
if (ch === '\\') {
|
|
130
|
+
out += ' ';
|
|
131
|
+
i += 2;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (ch === "'") {
|
|
135
|
+
inSingleQ = false;
|
|
136
|
+
}
|
|
137
|
+
out += ' ';
|
|
138
|
+
}
|
|
139
|
+
i++;
|
|
140
|
+
}
|
|
141
|
+
result.push(out);
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Classify the exception-handling flow of an except block body.
|
|
147
|
+
*
|
|
148
|
+
* Input: stripped lines (comments and strings already neutralized).
|
|
149
|
+
* Returns the strictest applicable classification.
|
|
150
|
+
*/
|
|
151
|
+
function classifyExceptionFlow(strippedBodyLines, exceptIndent) {
|
|
152
|
+
// Only consider lines that are within the except block's indentation scope
|
|
153
|
+
const blockLines = strippedBodyLines.filter(l => {
|
|
154
|
+
const t = l.trimStart();
|
|
155
|
+
if (t.length === 0)
|
|
156
|
+
return false;
|
|
157
|
+
const indent = l.length - t.length;
|
|
158
|
+
return indent > exceptIndent;
|
|
159
|
+
});
|
|
160
|
+
if (blockLines.length === 0)
|
|
161
|
+
return 'swallow';
|
|
162
|
+
const bodyText = blockLines.join('\n');
|
|
163
|
+
// Detect raise statements — only real Python raise keywords at statement level
|
|
164
|
+
// (not inside strings or comments, already stripped above)
|
|
165
|
+
const RAISE_STMT_RE = /^\s*raise\b/m;
|
|
166
|
+
const hasRaise = RAISE_STMT_RE.test(bodyText);
|
|
167
|
+
// Detect "raise X from e" or "raise NewException(" — transformed rethrow
|
|
168
|
+
const TRANSFORM_RAISE_RE = /^\s*raise\s+\w+\s*(?:\(|from)/m;
|
|
169
|
+
const hasTransformRaise = TRANSFORM_RAISE_RE.test(bodyText);
|
|
170
|
+
// Detect bare "raise" (re-raises current exception)
|
|
171
|
+
const BARE_RAISE_RE = /^\s*raise\s*$/m;
|
|
172
|
+
const hasBareRaise = BARE_RAISE_RE.test(bodyText);
|
|
173
|
+
// Detect conditional raise (raise inside if block at deeper indent)
|
|
174
|
+
const CONDITIONAL_RAISE_RE = /^\s+raise\b/m;
|
|
175
|
+
const hasConditionalRaise = !hasBareRaise && !hasTransformRaise && CONDITIONAL_RAISE_RE.test(bodyText);
|
|
176
|
+
const hasLogging = LOGGING_RE.test(bodyText);
|
|
177
|
+
if (hasBareRaise) {
|
|
178
|
+
// Clean re-raise — not a violation
|
|
179
|
+
return 'partial-rethrow'; // partial because could be log+reraise
|
|
180
|
+
}
|
|
181
|
+
if (hasTransformRaise)
|
|
182
|
+
return 'transformed-rethrow';
|
|
183
|
+
if (hasConditionalRaise)
|
|
184
|
+
return 'partial-rethrow';
|
|
185
|
+
if (!hasRaise && hasLogging)
|
|
186
|
+
return 'log-only';
|
|
187
|
+
if (!hasRaise && !hasLogging)
|
|
188
|
+
return 'swallow';
|
|
189
|
+
// raise present but not bare/transform/conditional — treat as partial
|
|
190
|
+
return 'partial-rethrow';
|
|
191
|
+
}
|
|
192
|
+
class PY003BroadExceptClause {
|
|
193
|
+
id = 'PY003';
|
|
194
|
+
name = 'Broad except clause swallowing errors';
|
|
195
|
+
policyRef = 'P017';
|
|
196
|
+
severity = 'BLOCKING';
|
|
197
|
+
languages = ['python'];
|
|
198
|
+
description = 'except Exception: blocks that neither re-raise nor log silently swallow all exceptions including system errors.';
|
|
199
|
+
check(filePath, sourceText) {
|
|
200
|
+
try {
|
|
201
|
+
const violations = [];
|
|
202
|
+
const lines = sourceText.split('\n');
|
|
203
|
+
for (let i = 0; i < lines.length; i++) {
|
|
204
|
+
const line = lines[i];
|
|
205
|
+
const match = BROAD_EXCEPT_RE.exec(line);
|
|
206
|
+
if (!match)
|
|
207
|
+
continue;
|
|
208
|
+
const exceptIndent = match[1].length;
|
|
209
|
+
// Collect the raw except block body lines (indented deeper than the except)
|
|
210
|
+
const rawBodyLines = [];
|
|
211
|
+
let j = i + 1;
|
|
212
|
+
while (j < lines.length) {
|
|
213
|
+
const bodyLine = lines[j];
|
|
214
|
+
const bodyTrimmed = bodyLine.trimStart();
|
|
215
|
+
// Empty line — continue collecting
|
|
216
|
+
if (bodyTrimmed.length === 0) {
|
|
217
|
+
rawBodyLines.push(bodyLine);
|
|
218
|
+
j++;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const bodyIndent = bodyLine.length - bodyTrimmed.length;
|
|
222
|
+
// If indent is less than or equal to the except indent, block ended
|
|
223
|
+
if (bodyIndent <= exceptIndent)
|
|
224
|
+
break;
|
|
225
|
+
rawBodyLines.push(bodyLine);
|
|
226
|
+
j++;
|
|
227
|
+
}
|
|
228
|
+
if (rawBodyLines.length === 0)
|
|
229
|
+
continue;
|
|
230
|
+
// ── AST-level analysis: strip comments and strings before checking ──
|
|
231
|
+
const strippedLines = stripCommentsAndStrings(rawBodyLines);
|
|
232
|
+
const flowClass = classifyExceptionFlow(strippedLines, exceptIndent);
|
|
233
|
+
// Only violations: swallow and log-only (log without re-raise)
|
|
234
|
+
// transformed-rethrow and partial-rethrow are handled by the engineer
|
|
235
|
+
if (flowClass === 'partial-rethrow' || flowClass === 'transformed-rethrow') {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
// Bare re-raise check: if bare raise exists in RAW lines (before stripping)
|
|
239
|
+
// this is not a swallow — the stripCommentsAndStrings already handles
|
|
240
|
+
// comment stripping, so we just trust classifyExceptionFlow here.
|
|
241
|
+
if (flowClass !== 'swallow' && flowClass !== 'log-only')
|
|
242
|
+
continue;
|
|
243
|
+
const nonEmptyNonPass = strippedLines
|
|
244
|
+
.map(l => l.trim())
|
|
245
|
+
.filter(l => l.length > 0 && l !== 'pass');
|
|
246
|
+
const confidence = flowClass === 'swallow'
|
|
247
|
+
? (nonEmptyNonPass.length === 0 ? 0.97 : 0.88)
|
|
248
|
+
: 0.82; // log-only
|
|
249
|
+
const evidence = line.slice(0, 120);
|
|
250
|
+
violations.push({
|
|
251
|
+
ruleId: this.id,
|
|
252
|
+
ruleName: this.name,
|
|
253
|
+
policyRef: this.policyRef,
|
|
254
|
+
severity: this.severity,
|
|
255
|
+
filePath,
|
|
256
|
+
line: i + 1,
|
|
257
|
+
column: exceptIndent + 1,
|
|
258
|
+
evidence: `${evidence} [flow:${flowClass}]`,
|
|
259
|
+
operationalRisk: `except Exception: block classified as '${flowClass}'. ` +
|
|
260
|
+
'Catches ALL exceptions (including SystemExit, KeyboardInterrupt, MemoryError) without ' +
|
|
261
|
+
're-raising. Silent failures make debugging impossible and hide operational issues.',
|
|
262
|
+
remediation: 'Either re-raise after handling: `except Exception as e: logger.error(e); raise` ' +
|
|
263
|
+
'or narrow the exception type. Avoid bare `except Exception` without at minimum logging.',
|
|
264
|
+
determinism: 'deterministic-structural',
|
|
265
|
+
confidence,
|
|
266
|
+
language: 'python',
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return violations;
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
exports.PY003BroadExceptClause = PY003BroadExceptClause;
|
|
277
|
+
//# sourceMappingURL=PY003-broad-except-clause.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY003-broad-except-clause.js","sourceRoot":"","sources":["../../../src/structural-rules/python/PY003-broad-except-clause.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StructuralRule, StructuralViolation, RuleLanguage } from '../types';
|
|
2
|
+
export declare class PY004SwallowedAsyncException 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=PY004-swallowed-async-exception.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PY004-swallowed-async-exception.d.ts","sourceRoot":"","sources":["../../../src/structural-rules/python/PY004-swallowed-async-exception.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAc7E,qBAAa,4BAA6B,YAAW,cAAc;IACjE,EAAE,SAAW;IACb,IAAI,SAAiC;IACrC,SAAS,SAAU;IACnB,QAAQ,EAAG,UAAU,CAAU;IAC/B,SAAS,EAAE,YAAY,EAAE,CAAc;IACvC,WAAW,SAEsE;IAEjF,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAkJnE"}
|
|
@@ -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
|