@nidvue/teamwork 1.1.0 → 1.1.2
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/CHANGELOG.md +57 -1
- package/package.json +1 -1
- package/skills/teamwork/SKILL.md +4 -2
- package/skills/teamwork/references/Remotes.md +4 -1
- package/skills/teamwork/scripts/privacy_scan.py +91 -80
- package/skills/teamwork/src/teamwork/engine.py +28 -1
- package/skills/teamwork/src/teamwork/version.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,7 +9,63 @@ All notable changes to this project are documented in this file.
|
|
|
9
9
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
10
10
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
11
11
|
|
|
12
|
-
## [Unreleased](https://github.com/Nidvue/Teamwork/compare/v1.
|
|
12
|
+
## [Unreleased](https://github.com/Nidvue/Teamwork/compare/v1.1.2...HEAD)
|
|
13
|
+
|
|
14
|
+
## [1.1.2](https://github.com/Nidvue/Teamwork/compare/v1.1.1...v1.1.2) - 2026-10-20
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **The skill now declares its interpreter.** `SKILL.md` carried no
|
|
19
|
+
`compatibility` field, so a host without python3 loaded the skill and only found
|
|
20
|
+
out at the first `teamwork.py` call. It now states
|
|
21
|
+
`compatibility: python3 3.9 or newer on PATH - the engine is stdlib-only, so
|
|
22
|
+
there is no pip install and no venv; git is required for every branch, commit
|
|
23
|
+
and PR operation`, and
|
|
24
|
+
`tests/unit/test_skill_contracts.py::test_the_skill_declares_the_agent_skills_contract`
|
|
25
|
+
fails when the field is missing or advertises an interpreter `pyproject.toml`
|
|
26
|
+
does not support.
|
|
27
|
+
- **A self-contradictory review rule.** The re-submission bullet read "there is no
|
|
28
|
+
round limit, but at fail, fix, re-check, re-submit, re-review until it passes" -
|
|
29
|
+
a truncated sentence that contradicted the 3-failure dispute rule stated 300
|
|
30
|
+
lines above it. It now says failures are counted and at 3 the Reviewer opens a
|
|
31
|
+
dispute, which is what the tribunal paragraph, `AGENTS.md` and
|
|
32
|
+
`references/Dissent.md` all say.
|
|
33
|
+
- **Nine tests were unreachable on NixOS and Guix.**
|
|
34
|
+
`tests/unit/test_skill_refresh.py` ran every `git` call with a hard-coded
|
|
35
|
+
`PATH=/usr/bin:/bin:/usr/local/bin`, where git does not exist on those
|
|
36
|
+
distributions, so the nine fixtures errored instead of testing the refresh path.
|
|
37
|
+
The ambient `PATH` is now prepended and the standard directories are kept as a
|
|
38
|
+
floor. On this machine: 9 errors -> 0.
|
|
39
|
+
|
|
40
|
+
## [1.1.1](https://github.com/Nidvue/Teamwork/compare/v1.1.0...v1.1.1) - 2026-10-20
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- **npm package republished as 1.1.1**: the first `npm publish` of
|
|
45
|
+
`@nidvue/teamwork@1.1.0` exited 0 but only *staged* the version on the registry
|
|
46
|
+
(`dist-tags.latest` stayed at the `0.0.0-stage` placeholder, the packument never
|
|
47
|
+
appeared, and `npm stage list` returned no stage-id to reject). A second publish
|
|
48
|
+
of that version is refused with `E409 Cannot publish over previously staged
|
|
49
|
+
version`. This is the phantom-staged-publish registry bug tracked as
|
|
50
|
+
[npm/cli#9889](https://github.com/npm/cli/issues/9889); the version number cannot
|
|
51
|
+
be reused, so the package ships as 1.1.1. No code, skill or protocol change -
|
|
52
|
+
the only differences from the 1.1.0 build are the `version` field and this entry.
|
|
53
|
+
|
|
54
|
+
### Security
|
|
55
|
+
|
|
56
|
+
- **The privacy scanner publishes no character of a matched value**: a finding
|
|
57
|
+
line now names the rule - `<location>: [<severity>/<rule>] <redacted-<rule>>` -
|
|
58
|
+
instead of a partly masked preview (`ghp********************************QQQ`
|
|
59
|
+
used to hand the first and last three characters of a credential to every reader
|
|
60
|
+
of a CI log, which is the disclosure the scanner exists to prevent). The raw
|
|
61
|
+
value is no longer an item of the finding record at all: it is held on the
|
|
62
|
+
`Finding` object as `.raw`, so no reporting sink - the text line, `--json`, the
|
|
63
|
+
archive mask - can serialise it by accident. `--reveal` (stable fingerprint) and
|
|
64
|
+
`--reveal-to` (`0600` file) are unchanged, and the `--json` evidence is
|
|
65
|
+
rule-named too. This closes the two open `py/clear-text-logging-sensitive-data`
|
|
66
|
+
code-scanning alerts on `scripts/privacy_scan.py` by removing the dataflow
|
|
67
|
+
rather than by a dismissal or a query exclusion, and `SCANNER_VERSION` moves to
|
|
68
|
+
`1.4.0`. Dead code that took a raw value (`_clip`) is gone.
|
|
13
69
|
|
|
14
70
|
## [1.1.0](https://github.com/Nidvue/Teamwork/releases/tag/v1.1.0) - 2026-10-20
|
|
15
71
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nidvue/teamwork",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Nidvue Teamwork - an autonomous, self-governing agent company for pi: CEO-led decomposition, parallel lanes, two-stage review, hash-chained audit ledger and layered role-owned tests. Python 3.9+, stdlib only.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/skills/teamwork/SKILL.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: teamwork
|
|
3
3
|
description: "Nidvue Teamwork - run a user prompt to completion with an autonomous multi-agent company: CEO, CTO, COO, CFO, Architect, Product Manager, Project Manager, Designer, Release Engineer, Technical Writer, Data Engineer, Developer(s), Tester(s), Reviewer and Security Auditor. Use when the user invokes /teamwork, asks for a team of sub-agents with roles, wants an org chart of agents that plans, builds, tests and iterates until the goal is met, or wants long-running parallel work with per-agent memory. The user talks only to the CEO; every other agent communicates internally."
|
|
4
|
+
compatibility: "python3 3.9 or newer on PATH - the engine is stdlib-only, so there is no pip install and no venv; git is required for every branch, commit and PR operation."
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# Nidvue Teamwork - `teamwork` skill
|
|
@@ -696,8 +697,9 @@ Mechanical enforcement:
|
|
|
696
697
|
- a `review --result pass` appends the accepted result to
|
|
697
698
|
`docs/Team/Archive.md` (what, who, verified how, which revision) - the team's
|
|
698
699
|
evidence ledger. Read it with `teamwork.py archive [--agent X]`.
|
|
699
|
-
- a re-submitted fix is reviewed from scratch
|
|
700
|
-
|
|
700
|
+
- a re-submitted fix is reviewed from scratch - fail, fix, self-check, re-submit,
|
|
701
|
+
re-review until it passes - and the failures are counted, so at 3 the Reviewer
|
|
702
|
+
opens a dispute and the tribunal rules instead of the loop spinning.
|
|
701
703
|
- if a finding turns into a design disagreement, use the dispute protocol.
|
|
702
704
|
|
|
703
705
|
This applies to code, unit tests, integration/E2E tests, documentation, design
|
|
@@ -118,7 +118,10 @@ Security), an open `error`/`critical`/`high` alert on the default branch is a
|
|
|
118
118
|
known defect **in the shipped revision**: the code is already public, so the
|
|
119
119
|
alert is a release blocker, not a backlog item. Two measured examples on this
|
|
120
120
|
repository's own remote, both `py/clear-text-logging-sensitive-data` on
|
|
121
|
-
`scripts/privacy_scan.py`, both `error`, both on `main` with no task and no owner
|
|
121
|
+
`scripts/privacy_scan.py`, both `error`, both on `main` with no task and no owner -
|
|
122
|
+
and both only closed when the value stopped reaching the sink at all: the raw
|
|
123
|
+
value left the finding record, and the report named the rule instead of printing a
|
|
124
|
+
partly masked preview.
|
|
122
125
|
|
|
123
126
|
The Release Engineer owns the gate:
|
|
124
127
|
|
|
@@ -48,11 +48,13 @@ Design notes
|
|
|
48
48
|
* Stdlib only, no network, no third-party import.
|
|
49
49
|
* The scanner is itself published, so it must not become the leak it detects.
|
|
50
50
|
Private domains are therefore not stored as plain text (they are reconstructed
|
|
51
|
-
from code points at runtime) and matched
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
from code points at runtime) and a matched value never reaches a log in any
|
|
52
|
+
form: the report names the rule (``<redacted-rule>``) and no more, so a failing
|
|
53
|
+
CI log cannot republish the secret, not even a prefix of it. The raw value is
|
|
54
|
+
held outside the report record (see ``Finding``), so no reporting sink can
|
|
55
|
+
serialise it by accident. ``--reveal`` prints a stable fingerprint (SHA-256
|
|
56
|
+
prefix) instead and ``--reveal-to PATH`` exports the raw values to a ``0600``
|
|
57
|
+
file for local remediation only.
|
|
56
58
|
* The allowlist file (``--allow``) holds one regular expression per line with an
|
|
57
59
|
optional ``# reason`` suffix. A finding is exempt when its matched text matches
|
|
58
60
|
any allowlist expression. Keep it minimal and justify each entry.
|
|
@@ -63,6 +65,11 @@ Unmasked values are **never** written to stdout or stderr. A log line is a
|
|
|
63
65
|
broadcast sink - CI logs are public, terminals are recorded, and ``tee``ed
|
|
64
66
|
output outlives the session - so the scanner has no flag that prints a secret.
|
|
65
67
|
|
|
68
|
+
* The report line for a finding is
|
|
69
|
+
``<location>: [<severity>/<rule>] <redacted-<rule>>``: it says which rule fired
|
|
70
|
+
where, and deliberately no character of the value - a partly masked preview
|
|
71
|
+
still publishes the first and last characters of a credential to every reader
|
|
72
|
+
of the log, which is the disclosure this scanner exists to prevent.
|
|
66
73
|
* ``--reveal`` adds ``(sha256:<12 hex>)`` to each finding line. The fingerprint
|
|
67
74
|
is a stable, unsalted SHA-256 prefix: it identifies which finding is which and
|
|
68
75
|
lets an operator confirm a rotated value, but it cannot be used as the
|
|
@@ -91,7 +98,7 @@ import sys
|
|
|
91
98
|
from collections import Counter
|
|
92
99
|
from pathlib import Path
|
|
93
100
|
|
|
94
|
-
SCANNER_VERSION = "1.
|
|
101
|
+
SCANNER_VERSION = "1.4.0"
|
|
95
102
|
|
|
96
103
|
# --------------------------------------------------------------------------- #
|
|
97
104
|
# Neutral identity / allowlisted email domains
|
|
@@ -511,14 +518,6 @@ def _email_allowed(value: str) -> bool:
|
|
|
511
518
|
return any(domain.endswith(suffix) for suffix in _ALLOWED_EMAIL_SUFFIXES)
|
|
512
519
|
|
|
513
520
|
|
|
514
|
-
def _mask(value: str) -> str:
|
|
515
|
-
if len(value) <= 4:
|
|
516
|
-
return "*" * len(value)
|
|
517
|
-
if len(value) <= 10:
|
|
518
|
-
return value[0] + "*" * (len(value) - 2) + value[-1]
|
|
519
|
-
return value[:3] + "*" * (len(value) - 6) + value[-3:]
|
|
520
|
-
|
|
521
|
-
|
|
522
521
|
def _fingerprint(value: str, length: int = 12) -> str:
|
|
523
522
|
"""A stable, non-usable identifier for a matched value.
|
|
524
523
|
|
|
@@ -562,43 +561,49 @@ def write_reveal_file(path, pairs):
|
|
|
562
561
|
return written
|
|
563
562
|
|
|
564
563
|
|
|
565
|
-
def _clip(line: str, value: str, width: int = 160) -> str:
|
|
566
|
-
"""A short, single-line context snippet around the match."""
|
|
567
|
-
index = line.find(value)
|
|
568
|
-
if index < 0:
|
|
569
|
-
return line[:width]
|
|
570
|
-
start = max(0, index - 40)
|
|
571
|
-
end = min(len(line), index + len(value) + 40)
|
|
572
|
-
snippet = line[start:end].strip()
|
|
573
|
-
if start > 0:
|
|
574
|
-
snippet = "..." + snippet
|
|
575
|
-
if end < len(line):
|
|
576
|
-
snippet = snippet + "..."
|
|
577
|
-
return snippet[:width]
|
|
578
|
-
|
|
579
|
-
|
|
580
564
|
# --------------------------------------------------------------------------- #
|
|
581
565
|
# Findings
|
|
582
566
|
# --------------------------------------------------------------------------- #
|
|
583
567
|
|
|
584
568
|
|
|
569
|
+
class Finding(dict):
|
|
570
|
+
"""One report row, with the raw matched value held outside the mapping.
|
|
571
|
+
|
|
572
|
+
Every reporting sink serialises *items*: ``json.dumps(report)`` walks the
|
|
573
|
+
dict, the text report reads ``evidence``, and both end up in a CI log. A
|
|
574
|
+
value kept as an attribute therefore cannot be published by any of them, so
|
|
575
|
+
"the log never carries a secret" is a property of the shape rather than a
|
|
576
|
+
promise held by one ``pop`` before one ``print``. Everything that needs the
|
|
577
|
+
value itself - ``--reveal``, ``--reveal-to`` and the archive mask - reads
|
|
578
|
+
``.raw`` explicitly, which is greppable and reviewable in one place each.
|
|
579
|
+
"""
|
|
580
|
+
|
|
581
|
+
__slots__ = ("raw",)
|
|
582
|
+
|
|
583
|
+
def __init__(self, items, raw=""):
|
|
584
|
+
super().__init__(items)
|
|
585
|
+
self.raw = raw
|
|
586
|
+
|
|
587
|
+
|
|
585
588
|
def make_finding(rule_id, severity, path, line_no, value, line, source, commit=None):
|
|
586
|
-
|
|
587
|
-
#
|
|
588
|
-
#
|
|
589
|
-
|
|
590
|
-
return
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
589
|
+
# Not one character of the value, and never the surrounding line: the report
|
|
590
|
+
# names the rule the way the archive mask does, because a partly masked
|
|
591
|
+
# preview still hands the first and last characters of a credential to
|
|
592
|
+
# everyone who can read the log.
|
|
593
|
+
return Finding(
|
|
594
|
+
{
|
|
595
|
+
"rule": rule_id,
|
|
596
|
+
"severity": severity,
|
|
597
|
+
"path": path,
|
|
598
|
+
"line": line_no,
|
|
599
|
+
"source": source,
|
|
600
|
+
"commit": commit,
|
|
601
|
+
"location": location(path, line_no, commit),
|
|
602
|
+
"evidence": f"<redacted-{rule_id}>",
|
|
603
|
+
"context": "",
|
|
604
|
+
},
|
|
605
|
+
value,
|
|
606
|
+
)
|
|
602
607
|
|
|
603
608
|
|
|
604
609
|
def location(path, line_no, commit=None):
|
|
@@ -861,18 +866,21 @@ def config_path_finding(rel, source, allow=(), commit=None):
|
|
|
861
866
|
if any(pattern.search(rel) for pattern in allow):
|
|
862
867
|
return None
|
|
863
868
|
local_state = reason.startswith("local state")
|
|
864
|
-
return
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
869
|
+
return Finding(
|
|
870
|
+
{
|
|
871
|
+
"rule": "local-state" if local_state else "local-config",
|
|
872
|
+
"severity": "high" if local_state else "critical",
|
|
873
|
+
"path": rel,
|
|
874
|
+
"line": 0,
|
|
875
|
+
"source": source,
|
|
876
|
+
"commit": commit,
|
|
877
|
+
"location": rel,
|
|
878
|
+
# A path is the finding, not a credential: naming it is the report.
|
|
879
|
+
"evidence": rel,
|
|
880
|
+
"context": reason,
|
|
881
|
+
},
|
|
882
|
+
rel,
|
|
883
|
+
)
|
|
876
884
|
|
|
877
885
|
|
|
878
886
|
def scan_tree(root, allow, stats=None):
|
|
@@ -888,18 +896,20 @@ def scan_tree(root, allow, stats=None):
|
|
|
888
896
|
data = full.read_bytes()
|
|
889
897
|
except OSError as exc:
|
|
890
898
|
findings.append(
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
899
|
+
Finding(
|
|
900
|
+
{
|
|
901
|
+
"rule": "unreadable-tracked-file",
|
|
902
|
+
"severity": "medium",
|
|
903
|
+
"path": rel,
|
|
904
|
+
"line": 0,
|
|
905
|
+
"source": "tree",
|
|
906
|
+
"commit": None,
|
|
907
|
+
"location": rel,
|
|
908
|
+
"evidence": rel,
|
|
909
|
+
"context": f"tracked file could not be read: {exc}",
|
|
910
|
+
},
|
|
911
|
+
rel,
|
|
912
|
+
)
|
|
903
913
|
)
|
|
904
914
|
continue
|
|
905
915
|
findings.extend(_scan_bytes(data, rel, "tree", allow))
|
|
@@ -1016,7 +1026,7 @@ def dedupe(findings):
|
|
|
1016
1026
|
seen = set()
|
|
1017
1027
|
unique = []
|
|
1018
1028
|
for finding in findings:
|
|
1019
|
-
key = (finding["rule"], finding["location"], finding
|
|
1029
|
+
key = (finding["rule"], finding["location"], finding.raw, finding.get("commit") or "")
|
|
1020
1030
|
if key in seen:
|
|
1021
1031
|
continue
|
|
1022
1032
|
seen.add(key)
|
|
@@ -1103,13 +1113,12 @@ def main(argv=None):
|
|
|
1103
1113
|
reveal_path = args.reveal_to
|
|
1104
1114
|
reveal_values = []
|
|
1105
1115
|
for finding in findings:
|
|
1106
|
-
|
|
1107
|
-
if match is None:
|
|
1116
|
+
if not finding.raw:
|
|
1108
1117
|
continue
|
|
1109
1118
|
if args.reveal:
|
|
1110
|
-
finding["fingerprint"] = _fingerprint(
|
|
1119
|
+
finding["fingerprint"] = _fingerprint(finding.raw)
|
|
1111
1120
|
if reveal_path:
|
|
1112
|
-
reveal_values.append((finding,
|
|
1121
|
+
reveal_values.append((finding, finding.raw))
|
|
1113
1122
|
|
|
1114
1123
|
reveal_written = 0
|
|
1115
1124
|
if reveal_path:
|
|
@@ -1151,16 +1160,18 @@ def main(argv=None):
|
|
|
1151
1160
|
else:
|
|
1152
1161
|
if not args.quiet:
|
|
1153
1162
|
for finding in findings:
|
|
1154
|
-
# The
|
|
1155
|
-
# sink. --reveal only adds
|
|
1163
|
+
# The value is never printed in any form: stdout is a broadcast
|
|
1164
|
+
# sink, so the report names the rule instead. --reveal only adds
|
|
1165
|
+
# a non-usable fingerprint.
|
|
1156
1166
|
shown = finding["evidence"]
|
|
1157
1167
|
fingerprint = finding.get("fingerprint")
|
|
1158
1168
|
if fingerprint:
|
|
1159
1169
|
shown = f"{shown} ({fingerprint})"
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1170
|
+
line = f"{finding['location']}: [{finding['severity']}/{finding['rule']}] {shown}"
|
|
1171
|
+
detail = finding.get("detail", "")
|
|
1172
|
+
if detail:
|
|
1173
|
+
line = f"{line} {detail}"
|
|
1174
|
+
print(line)
|
|
1164
1175
|
state = "FAIL" if findings else "PASS"
|
|
1165
1176
|
print(f"privacy_scan: {state} - {len(findings)} finding(s) in {', '.join(modes)}")
|
|
1166
1177
|
return 1 if findings else 0
|
|
@@ -3214,6 +3214,30 @@ def default_audit_range(project: Path) -> str:
|
|
|
3214
3214
|
return "HEAD"
|
|
3215
3215
|
|
|
3216
3216
|
|
|
3217
|
+
def rewrite_safe_range(project: Path, rev_range: str) -> str:
|
|
3218
|
+
"""Narrow ``A..B`` to ``B`` when ``A`` no longer exists, and say so.
|
|
3219
|
+
|
|
3220
|
+
A push event hands CI the revision the branch had before the push. When that
|
|
3221
|
+
push *rewrote* history - the sanctioned repair for a commit carrying a
|
|
3222
|
+
personal mailbox - the old revision is gone, ``git log A..B`` exits 128 with
|
|
3223
|
+
"Invalid revision range", and the identity audit dies over a range that can no
|
|
3224
|
+
longer be measured. The new tip can still be audited, so audit it, name the
|
|
3225
|
+
rewrite, and never pretend the unmeasurable span was clean.
|
|
3226
|
+
"""
|
|
3227
|
+
if ".." not in rev_range:
|
|
3228
|
+
return rev_range
|
|
3229
|
+
base = rev_range.split("..", 1)[0].strip()
|
|
3230
|
+
if not base or base.startswith("-"):
|
|
3231
|
+
return rev_range
|
|
3232
|
+
if run_git(project, "cat-file", "-e", f"{base}^{{commit}}").returncode == 0:
|
|
3233
|
+
return rev_range
|
|
3234
|
+
print(
|
|
3235
|
+
f"# {base[:12]} is not an ancestor of this clone (history rewrite): "
|
|
3236
|
+
f"auditing the tip only, the span before it is unmeasurable"
|
|
3237
|
+
)
|
|
3238
|
+
return rev_range.split("..", 1)[1].strip() or "HEAD"
|
|
3239
|
+
|
|
3240
|
+
|
|
3217
3241
|
def audit_commit_identities(project: Path, rev_range: str, strict_authors: bool = False) -> int:
|
|
3218
3242
|
"""Fail when a commit is not authored and committed by the acting role.
|
|
3219
3243
|
|
|
@@ -3228,6 +3252,7 @@ def audit_commit_identities(project: Path, rev_range: str, strict_authors: bool
|
|
|
3228
3252
|
if rev_range.startswith("-"):
|
|
3229
3253
|
print(f"teamwork: invalid revision range {rev_range!r}")
|
|
3230
3254
|
return 2
|
|
3255
|
+
rev_range = rewrite_safe_range(project, rev_range)
|
|
3231
3256
|
proc = run_git(
|
|
3232
3257
|
project, "log", "--format=%H%x1f%an%x1f%ae%x1f%cn%x1f%ce%x1f%P%x1f%B%x1e", rev_range
|
|
3233
3258
|
)
|
|
@@ -6608,7 +6633,9 @@ def mask_secret_shapes(text: str) -> str:
|
|
|
6608
6633
|
# placeholder, code-shape and allow filters, so a placeholder the gate accepts
|
|
6609
6634
|
# (``${API_KEY}``, ``replace_me``) was rewritten in the immutable record.
|
|
6610
6635
|
for finding in scanner.scan_lines(text, "", "archive", rules, ()):
|
|
6611
|
-
value
|
|
6636
|
+
# The raw value is a Finding attribute, never a report item, so the
|
|
6637
|
+
# scanner cannot publish it and this mask reads it explicitly.
|
|
6638
|
+
value = finding.raw or ""
|
|
6612
6639
|
if not value:
|
|
6613
6640
|
continue
|
|
6614
6641
|
rule = finding["rule"]
|
|
@@ -6,7 +6,7 @@ that importing ``teamwork`` stays cheap.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
__version__ = "1.1.
|
|
9
|
+
__version__ = "1.1.2"
|
|
10
10
|
|
|
11
11
|
#: Distribution name on PyPI. The name ``teamwork`` is already taken there, so
|
|
12
12
|
#: the project ships as ``teamwork-agents`` while importing as ``teamwork``.
|